home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Mail / pine3.92 / pine / mailview.c < prev    next >
C/C++ Source or Header  |  1996-03-14  |  108KB  |  3,859 lines

  1. #if !defined(lint) && !defined(DOS)
  2. static char rcsid[] = "$Id: mailview.c,v 4.227 1996/03/15 07:13:42 hubert Exp $";
  3. #endif
  4. /*----------------------------------------------------------------------
  5.  
  6.             T H E    P I N E    M A I L   S Y S T E M
  7.  
  8.    Laurence Lundblade and Mike Seibel
  9.    Networks and Distributed Computing
  10.    Computing and Communications
  11.    University of Washington
  12.    Administration Builiding, AG-44
  13.    Seattle, Washington, 98195, USA
  14.    Internet: lgl@CAC.Washington.EDU
  15.              mikes@CAC.Washington.EDU
  16.  
  17.    Please address all bugs and comments to "pine-bugs@cac.washington.edu"
  18.  
  19.  
  20.    Pine and Pico are registered trademarks of the University of Washington.
  21.    No commercial use of these trademarks may be made without prior written
  22.    permission of the University of Washington.
  23.  
  24.    Pine, Pico, and Pilot software and its included text are Copyright
  25.    1989-1996 by the University of Washington.
  26.  
  27.    The full text of our legal notices is contained in the file called
  28.    CPYRIGHT, included with this distribution.
  29.  
  30.  
  31.    Pine is in part based on The Elm Mail System:
  32.     ***********************************************************************
  33.     *  The Elm Mail System  -  Revision: 2.13                             *
  34.     *                                                                     *
  35.     *             Copyright (c) 1986, 1987 Dave Taylor              *
  36.     *             Copyright (c) 1988, 1989 USENET Community Trust   *
  37.     ***********************************************************************
  38.  
  39.  
  40.   ----------------------------------------------------------------------*/
  41.  
  42. /*======================================================================
  43.      mailview.c
  44.      Implements the mailview screen
  45.      Also includes scrolltool used to display help text
  46.   ====*/
  47.  
  48.  
  49. #include "headers.h"
  50.  
  51.  
  52. /*----------------------------------------------------------------------
  53.     Saved state for scrolling text 
  54.  ----*/
  55. typedef struct scroll_text {
  56.     void *text;          /* Original text */
  57.     char **text_lines;   /* Lines to display */
  58.     FILE  *findex;     /* file containing line offsets in another file */
  59.     char  *fname;     /* name of file containing line offsets */
  60.     long top_text_line;  /* index into text array of line on top of screen */
  61.     long num_lines;      /* Calculated number lines of text to display */
  62.     int lines_allocated; /* size of array text_lines */
  63.     int screen_length;     /* screen length in lines of text displayed by
  64.               * scroll tool  (== PGSIZE). */
  65.     int screen_width;    /* screen width of current formatting */
  66.     int screen_start_line; /* First line on screen that we scroll text on */
  67.     int screen_other_lines;/* Line ons screen not used for scroll text */
  68.     short *line_lengths;   /* Lengths of lines for display, not \0 terminatd*/
  69.     SourceType  source;     /* How to interpret "text" field */
  70.     TextType    style;     /* scrolltool screen mode; message, news, etc. */
  71. } SCROLL_S;
  72.  
  73.  
  74. /*
  75.  * Definitions to help scrolltool
  76.  */
  77. #define SCROLL_LINES_ABOVE(X)    HEADER_ROWS(X)
  78. #define SCROLL_LINES_BELOW(X)    FOOTER_ROWS(X)
  79. #define    SCROLL_LINES(X)        max(((X)->ttyo->screen_rows               \
  80.              - SCROLL_LINES_ABOVE(X) - SCROLL_LINES_BELOW(X)), 0)
  81. #define    get_scroll_text_lines()    (scroll_state(SS_CUR)->num_lines)
  82.  
  83.  
  84. /*
  85.  * Definitions for various scroll state manager's functions
  86.  */
  87. #define    SS_NEW    1
  88. #define    SS_CUR    2
  89. #define    SS_FREE    3
  90.  
  91.  
  92. #define    PGSIZE(X)      (ps_global->ttyo->screen_rows - (X)->screen_other_lines)
  93. #define    ISRFCEOL(S)    (*(S) == '\015' && *((S)+1) == '\012')
  94.  
  95. #define TYPICAL_BIG_MESSAGE_LINES 200 
  96.  
  97.  
  98. /*
  99.  * Internal prototypes
  100.  */
  101. int        is_an_env_hdr PROTO((char *, int));
  102. void       format_env_hdr PROTO((MAILSTREAM *, long, ENVELOPE *, gf_io_t,
  103.                  char *, char *));
  104. int       format_raw_header PROTO((MAILSTREAM *, long, gf_io_t, char *));
  105. void       format_addr_string PROTO((MAILSTREAM *, long, char *, ADDRESS *,
  106.                      char *, gf_io_t));
  107. void       format_newsgroup_string PROTO((char *, char *, char *, gf_io_t));
  108. int       format_raw_hdr_string PROTO((char *, char *, gf_io_t, char *));
  109. int        delineate_this_header PROTO ((char *,char *,char *,char **,char **));
  110. void       set_scroll_text PROTO((SCROLL_S *, void *, long, SourceType,
  111.                   TextType));
  112. long       scroll_scroll_text PROTO((long, int));
  113. static int print_to_printer PROTO((void *, SourceType, char *));
  114. int       search_scroll_text PROTO((long, int, char *, Pos *));
  115. void       describe_mime PROTO((BODY *, char *, int, int));
  116. void       format_mime_size PROTO((char *, BODY *));
  117. ATTACH_S  *next_attachment PROTO(());
  118. void       zero_atmts PROTO((ATTACH_S *));
  119. void       zero_scroll_text PROTO((void));
  120. void       ScrollFile PROTO((long));
  121. long       make_file_index PROTO(());
  122. char      *show_multipart PROTO((MESSAGECACHE *, int));
  123. MimeShow   mime_show PROTO((BODY *));
  124. char      *part_desc PROTO((char *,BODY *, int));
  125. SCROLL_S  *scroll_state PROTO((int));
  126. int       search_text PROTO((int, long, int, char *, Pos *));
  127. void       format_scroll_text PROTO((void));
  128. void       redraw_scroll_text PROTO((void));
  129. void       update_scroll_titlebar PROTO((char *, TextType, long, int));
  130. #ifdef    _WINDOWS
  131. int       mswin_readscrollbuf PROTO((int));
  132. int       scroll_scroll_callback PROTO((int, long));
  133. #endif
  134.  
  135.  
  136.  
  137. /*----------------------------------------------------------------------
  138.      Format a buffer with the text of the current message for browser
  139.  
  140.     Args: ps - pine state structure
  141.   
  142.   Result: The scrolltool is called to display the message
  143.  
  144.   Loop here viewing mail until the folder changed or a command takes
  145. us to another screen. Inside the loop the message text is fetched and
  146. formatted into a buffer allocated for it.  These are passed to the
  147. scrolltool(), that displays the message and executes commands. It
  148. returns when it's time to display a different message, when we
  149. change folders, when it's time for a different screen, or when
  150. there are no more messages available.
  151.   ---*/
  152.  
  153. void
  154. mail_view_screen(ps)
  155.      struct pine *ps;
  156. {
  157.     char            last_was_full_header = 0, coerce_seen_bit;
  158.     long            last_message_viewed = -1L, raw_msgno;
  159.     int             we_cancel = 0;
  160.     MESSAGECACHE   *mc;
  161.     ENVELOPE       *env;
  162.     BODY           *body;
  163.     STORE_S        *store;
  164.     gf_io_t         pc;
  165.     SourceType        src = CharStar;
  166.  
  167.     dprint(1, (debugfile, "\n\n  -----  MAIL VIEW  -----\n"));
  168.  
  169.     /*----------------- Loop viewing messages ------------------*/
  170.     do {
  171.     /*
  172.      * Check total to make sure there's something to view.  Check it
  173.      * inside the loop to make sure everything wasn't expunged while
  174.      * we were viewing.  If so, make sure we don't just come back.
  175.      */
  176.     if(mn_get_total(ps->msgmap) <= 0L || !ps->mail_stream){
  177.         q_status_message(SM_ORDER, 0, 3, "No messages to read!");
  178.         ps->next_screen = (ps->prev_screen != mail_view_screen)
  179.                 ? ps->prev_screen : mail_index_screen;
  180.         break;
  181.     }
  182.  
  183.     we_cancel = busy_alarm(1, NULL, NULL, 0);
  184.  
  185.     if(mn_get_cur(ps->msgmap) <= 0L)
  186.       mn_set_cur(ps->msgmap, 1L);
  187.  
  188.     raw_msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
  189.     body      = NULL;
  190.     if(!(env = mail_fetchstructure(ps->mail_stream, raw_msgno, &body))
  191.        || !(mc = mail_elt(ps->mail_stream, raw_msgno))){
  192.         q_status_message1(SM_ORDER, 3, 3, "Error getting message %s data",
  193.                   comatose(mn_get_cur(ps->msgmap)));
  194.         dprint(1, (debugfile, "!!!! ERROR fetching %s of msg %ld\n",
  195.                env ? "elt" : "env",
  196.                mn_get_cur(ps_global->msgmap)));
  197.         ps->next_screen = (ps->prev_screen != mail_view_screen)
  198.                 ? ps->prev_screen : mail_index_screen;
  199.         break;
  200.     }
  201.  
  202.     if(!mc->seen){        /* count state change in check_point */
  203.         clear_index_cache_ent(mn_get_cur(ps->msgmap));
  204.         check_point_change();
  205.         ps_global->unseen_in_view = 1;
  206.     }
  207.     else
  208.       ps_global->unseen_in_view = 0;
  209.  
  210. #if    defined(DOS) && !defined(WIN32)
  211.     /* 
  212.      * Handle big text for DOS here.
  213.      *
  214.      * judging from 1000+ message folders around here, it looks
  215.      * like 9X% of messages fall in the < 8k range, so it
  216.      * seems like this is as good a place to draw the line as any.
  217.      *
  218.      * We ALSO need to divert all news articles we read to secondary
  219.      * storage as its possible we're using c-client's NNTP driver
  220.      * which returns BOGUS sizes UNTIL the whole thing is fetched.
  221.      * Note: this is more a deficiency in NNTP than in c-client.
  222.      */
  223.     src = (mc->rfc822_size > MAX_MSG_INCORE 
  224.            || strcmp(ps->mail_stream->dtb->name, "nntp") == 0)
  225.         ? FileStar : CharStar;
  226. #endif
  227.     store = so_get(src, NULL, EDIT_ACCESS);
  228.     gf_set_so_writec(&pc, store);
  229.  
  230.     (void) format_message(raw_msgno, env, body,
  231.                   (last_message_viewed != mn_get_cur(ps->msgmap)
  232.                    || last_was_full_header == 1) ? FM_NEW_MESS : 0,
  233.                   pc);
  234.  
  235.         last_message_viewed  = mn_get_cur(ps->msgmap);
  236.         last_was_full_header = ps->full_header;
  237.  
  238.         ps->next_screen = SCREEN_FUN_NULL;
  239.         scrolltool(so_text(store), "MESSAGE TEXT",
  240.                    MessageText, src, (ATTACH_S *)NULL);
  241.  
  242.     ps_global->unseen_in_view = 0;
  243.     so_give(&store);    /* free resources associated with store */
  244.     }
  245.     while(ps->next_screen == SCREEN_FUN_NULL);
  246.  
  247.     if(we_cancel)
  248.       cancel_busy_alarm(-1);
  249.  
  250.     ps->prev_screen = mail_view_screen;
  251. }
  252.  
  253.  
  254.  
  255. /*----------------------------------------------------------------------
  256.     Add lines to the attachments structure
  257.     
  258.   Args: body   -- body of the part being described
  259.         prefix -- The prefix for numbering the parts
  260.         num    -- The number of this specific part
  261.         should_show -- Flag indicating which of alternate parts should be shown
  262.  
  263. Result: The ps_global->attachments data structure is filled in. This
  264. is called recursively to descend through all the parts of a message. 
  265. The description strings filled in are malloced and should be freed.
  266.  
  267.   ----*/
  268. void
  269. describe_mime(body, prefix, num, should_show)
  270.     BODY *body;
  271.     char *prefix;
  272.     int   num, should_show;
  273. {
  274.     PART      *part;
  275.     char       numx[512], string[200], *description;
  276.     int        n, alt_to_show, named, use_viewer;
  277.     ATTACH_S  *a;
  278.     PARAMETER *param;
  279.  
  280.     if(!body)
  281.       return;
  282.  
  283.     if(body->type == TYPEMULTIPART){
  284.     alt_to_show = 0;
  285.         if(strucmp(body->subtype, "alternative") == 0){
  286.             /*---- Figure out which alternative part to display ---*/
  287.             for(part=body->contents.part, n=1; part; part=part->next, n++)
  288.           if(mime_show(&(part->body)) != ShowNone)
  289.         alt_to_show = n;
  290.     }
  291.  
  292.         for(part=body->contents.part, n=1; part; part=part->next, n++){
  293.             sprintf(numx, "%s%d.", prefix, n);
  294.             describe_mime(&(part->body),
  295.                           (part->body.type == TYPEMULTIPART) ? numx : prefix,
  296.                           n, (n == alt_to_show || alt_to_show == 0) ? 1 : 0);
  297.         }
  298.     }
  299.     else{
  300.         format_mime_size((a = next_attachment())->size, body);
  301.  
  302.     description = (body->description)
  303.                 ? body->description
  304.             : (body->type == TYPEMESSAGE && body->subtype
  305.                && strucmp(body->subtype, "rfc822") == 0
  306.                && body->contents.msg.env
  307.                && body->contents.msg.env->subject)
  308.                ? body->contents.msg.env->subject
  309.                : NULL;
  310.  
  311.         sprintf(string, "%s%s%.*s%s",
  312.                 type_desc(body->type, body->subtype, body->parameter, 0),
  313.                 description ? ", \"" : "",
  314.                 sizeof(string) - 20, 
  315.                 description ? description : "",
  316.                 description ? "\"": "");
  317.  
  318.         a->description = cpystr(string);
  319.         a->body        = body;
  320.     for(named = 0, param = body->parameter; param; param = param->next)
  321.       if(strucmp(param->attribute, "name") == 0){
  322.           /* 
  323.            * before we buy the name param, make sure it's not a
  324.            * name assigned by a particularly helpful UA.  cool.
  325.            */
  326.           named = strucmp(param->value, "Message Body");
  327.           break;
  328.       }
  329.  
  330.     /*
  331.      * Make sure we have the tools available to display the
  332.      * type/subtype, *AND* that we can decode it if needed.
  333.      * Of course, if it's text, we display it anyway in the
  334.      * mail_view_screen so put off testing mailcap until we're
  335.      * explicitly asked to display that segment 'cause it could
  336.      * be expensive to test...
  337.      */
  338.     use_viewer = 0;
  339.         a->can_display = (body->type == TYPETEXT
  340.               && !strucmp(body->subtype, "plain") && !named)
  341.                ? CD_DEFERRED
  342.                  : (mime_can_display(body->type, body->subtype,
  343.                          body->parameter, &use_viewer)
  344.                 && body->encoding < ENCOTHER)
  345.                   ? CD_GOFORIT
  346.                   : CD_NOCANDO;
  347.  
  348.     a->use_external_viewer = use_viewer;
  349.         a->shown = (((body->type == TYPETEXT
  350.               && ((!(*prefix && strcmp(prefix, "1.")) && num == 1)
  351.               || !(named || use_viewer)))
  352.             || body->type == TYPEMESSAGE)
  353.             && a->can_display != CD_NOCANDO
  354.             && should_show);
  355.     a->number = (char *)fs_get((strlen(prefix) + 16)* sizeof(char));
  356.         sprintf(a->number, "%s%d",prefix, num);
  357.         (a+1)->description = NULL;
  358.         if(body->type == TYPEMESSAGE
  359.        && body->subtype && strucmp(body->subtype, "rfc822") == 0){
  360.         body = body->contents.msg.body;
  361.         sprintf(numx, "%s%d.", prefix, num);
  362.         describe_mime(body, numx, 1, should_show);
  363.         }
  364.     }
  365. }
  366.  
  367.  
  368.  
  369. /*----------------------------------------------------------------------
  370.   Return a pointer to the next attachment struct
  371.     
  372.   Args: none
  373.  
  374.   ----*/
  375. ATTACH_S *
  376. next_attachment()
  377. {
  378.     ATTACH_S *a;
  379.     int       n;
  380.  
  381.     for(a = ps_global->atmts; a->description; a++)
  382.       ;
  383.  
  384.     if((n = a - ps_global->atmts) + 1 >= ps_global->atmts_allocated){
  385.     ps_global->atmts_allocated *= 2;
  386.     fs_resize((void **)&ps_global->atmts,
  387.           ps_global->atmts_allocated * sizeof(ATTACH_S));
  388.     a = &ps_global->atmts[n];
  389.     }
  390.  
  391.     return(a);
  392. }
  393.  
  394.  
  395.  
  396. /*----------------------------------------------------------------------
  397.    Zero out the attachments structure and free up storage
  398.   ----*/
  399. void
  400. zero_atmts(atmts)
  401.      ATTACH_S *atmts;
  402. {
  403.     ATTACH_S *a;
  404.  
  405.     for(a = atmts; a->description != NULL; a++){
  406.     fs_give((void **)&(a->description));
  407.     fs_give((void **)&(a->number));
  408.     }
  409.  
  410.     atmts->description = NULL;
  411. }
  412.  
  413.  
  414. char *
  415. body_type_names(t)
  416.     int t;
  417. {
  418.     static char body_type[32];
  419.     char   *p;
  420.  
  421.     strncpy(body_type,                /* copy the given type */
  422.         (t > -1 && t < TYPEMAX && body_types[t])
  423.           ? body_types[t] : "Other", 31);
  424.  
  425.     for(p = body_type + 1; *p; p++)        /* make it presentable */
  426.       if(isupper(*p))
  427.     *p = tolower(*p);
  428.  
  429.     return(body_type);                /* present it */
  430. }
  431.  
  432.  
  433. /*----------------------------------------------------------------------
  434.   Mapping table use to neatly display charset parameters
  435.  ----*/
  436.  
  437. static struct set_names {
  438.     char *rfcname,
  439.      *humanname;
  440. } charset_names[] = {
  441.     {"US-ASCII",        "Plain Text"},
  442.     {"ISO-8859-1",        "Latin 1"},
  443.     {"ISO-8859-2",        "Latin 2"},
  444.     {"ISO-8859-3",        "Latin 3"},
  445.     {"ISO-8859-4",        "Latin 4"},
  446.     {"ISO-8859-5",        "Latin & Cyrillic"},
  447.     {"ISO-8859-6",        "Latin & Arabic"},
  448.     {"ISO-8859-7",        "Latin & Greek"},
  449.     {"ISO-8859-8",        "Latin & Hebrew"},
  450.     {"ISO-8859-9",        "Latin 5"},
  451.     {"X-ISO-8859-10",        "Latin 6"},
  452.     {"KOI8-R",            "Latin & Russian"},
  453.     {"VISCII",            "Latin & Vietnamese"},
  454.     {"ISO-2022-JP",        "Latin & Japanese"},
  455.     {"ISO-2022-KR",        "Latin & Korean"},
  456.     {"UNICODE-1-1",        "Unicode"},
  457.     {"UNICODE-1-1-UTF-7",    "Mail-safe Unicode"},
  458.     {"ISO-2022-JP-2",        "Multilingual"},
  459.     {NULL,            NULL}
  460. };
  461.  
  462.  
  463. /*----------------------------------------------------------------------
  464.   Return a nicely formatted discription of the type of the part
  465.  ----*/
  466.  
  467. char *
  468. type_desc(type, subtype, params, full)
  469.      int type, full;
  470.      char *subtype;
  471.      PARAMETER *params;
  472. {
  473.     static char  type_d[200];
  474.     int         i;
  475.     char    *p;
  476.  
  477.     p = type_d;
  478.     sstrcpy(&p, body_type_names(type));
  479.     if(full && subtype){
  480.     *p++ = '/';
  481.     sstrcpy(&p, subtype);
  482.     }
  483.  
  484.     switch(type) {
  485.       case TYPETEXT:
  486.         while(params && strucmp(params->attribute,"charset") != 0)
  487.           params = params->next;
  488.  
  489.         if(params){
  490.         for(i = 0; charset_names[i].rfcname; i++)
  491.           if(!strucmp(params->value, charset_names[i].rfcname)){
  492.           if(!strucmp(params->value, ps_global->VAR_CHAR_SET)
  493.              || !strucmp(params->value, "us-ascii"))
  494.             i = -1;
  495.  
  496.           break;
  497.           }
  498.  
  499.         if(i >= 0){            /* charset to write */
  500.         sstrcpy(&p, " (charset: ");
  501.         sstrcpy(&p, charset_names[i].rfcname
  502.                   ? charset_names[i].rfcname : "Unknown");
  503.         if(full){
  504.             sstrcpy(&p, " \"");
  505.             sstrcpy(&p, charset_names[i].humanname
  506.                 ? charset_names[i].humanname : params->value);
  507.             *p++ = '\"';
  508.         }
  509.  
  510.         sstrcpy(&p, ")");
  511.         }
  512.         }
  513.  
  514.         break;
  515.  
  516.       case TYPEAPPLICATION:
  517.         if(full && subtype && strucmp(subtype, "octet-stream") == 0){
  518.             while(params && strucmp(params->attribute,"name"))
  519.               params = params->next;
  520.  
  521.         if(params && params->value)
  522.           sprintf(p, " (Name: \"%.100s\")", params->value);
  523.         }
  524.  
  525.         break;
  526.  
  527.       case TYPEMESSAGE:
  528.     if(full && subtype && strucmp(subtype, "external-body") == 0){
  529.             while(params && strucmp(params->attribute,"access-type"))
  530.               params = params->next;
  531.  
  532.         if(params && params->value)
  533.           sprintf(p, " (%s%.100s)", full ? "Access: " : "",
  534.               params->value);
  535.     }
  536.  
  537.     break;
  538.  
  539.       default:
  540.         break;
  541.     }
  542.  
  543.     return(type_d);
  544. }
  545.      
  546.  
  547. void
  548. format_mime_size(string, b)
  549.      char *string;
  550.      BODY *b;
  551. {
  552.     char tmp[10], *p = NULL;
  553.  
  554.     *string++ = ' ';
  555.     switch(b->encoding){
  556.       case ENCBASE64 :
  557.     if(b->type == TYPETEXT)
  558.       *(string-1) = '~';
  559.  
  560.     strcpy(p = string, byte_string((3 * b->size.bytes) / 4));
  561.     break;
  562.  
  563.       default :
  564.       case ENCQUOTEDPRINTABLE :
  565.     *(string-1) = '~';
  566.  
  567.       case ENC8BIT :
  568.       case ENC7BIT :
  569.     if(b->type == TYPETEXT)
  570.       sprintf(string, "%s lines", comatose(b->size.lines));
  571.     else
  572.       strcpy(p = string, byte_string(b->size.bytes));
  573.  
  574.     break;
  575.     }
  576.  
  577.  
  578.     if(p){
  579.     for(; *p && (isdigit(*p) || ispunct(*p)); p++)
  580.       ;
  581.  
  582.     sprintf(tmp, " %-5.5s",p);
  583.     strcpy(p, tmp);
  584.     }
  585. }
  586.  
  587.         
  588.  
  589. /*----------------------------------------------------------------------
  590.    Determine if we can show all, some or none of the parts of a body
  591.  
  592. Args: body --- The message body to check
  593.  
  594. Returns: ShowAll, ShowPart or ShowNone depending on how much of the body
  595.     can be shown 
  596.  ----*/     
  597. MimeShow
  598. mime_show(body)
  599.      BODY *body;
  600. {
  601.     int   sp, sn, sa;
  602.     PART *p;
  603.  
  604.     if(body == NULL)
  605.       return(ShowNone);
  606.  
  607.     switch(body->type) {
  608.       default:
  609.         return(mime_can_display(body->type,
  610.             body->subtype,body->parameter,(int *)NULL)== 1 ?
  611.                ShowAll:
  612.                ShowNone);
  613.  
  614.       case TYPEMESSAGE:
  615.         return(mime_show(body->contents.msg.body) == ShowAll ? 
  616.                ShowAll:
  617.                ShowParts);
  618.  
  619.       case TYPEMULTIPART:
  620.         sp = sn = sa = 0;
  621.         for(p = body->contents.part; p != NULL; p = p->next) {
  622.             switch(mime_show(&(p->body))) {
  623.               case ShowAll:
  624.                 sa++;
  625.                 break;
  626.               case ShowParts:
  627.                 sp++;
  628.                 break;
  629.               case ShowNone:
  630.                 sn++;
  631.                 break;
  632.             }
  633.         }
  634.  
  635.         if(sa == 0)
  636.           if(sp == 0)
  637.             return(ShowNone);
  638.           else
  639.             return(ShowParts);
  640.         else
  641.           if(sn == 0)
  642.             return(ShowAll);
  643.           else
  644.             return(ShowParts);
  645.     }
  646. }
  647.         
  648.  
  649.  
  650. /*----------------------------------------------------------------------
  651.    Format a message message for viewing
  652.  
  653.  Args: msgno -- The number of the message to view
  654.        env   -- pointer to the message's envelope
  655.        body  -- pointer to the message's body
  656.        flgs  -- possible flags:
  657.                 FM_NEW_MESS -- flag indicating a different message being
  658.                    formatted than was formatted last time 
  659.                    function was called
  660.         FM_DO_PRINT -- Indicates formatted text is bound for
  661.                    something other than display by pine
  662.                    (printing, export, etc)
  663.  
  664. Result: Returns true if no problems encountered, else false.
  665.  
  666. First the envelope is formatted; next a list of all attachments is
  667. formatted if there is more than one. Then all the body parts are
  668. formatted, fetching them as needed. This includes headers of included
  669. message. Richtext is also formatted. An entry is made in the text for
  670. parts that are not displayed or can't be displayed.  source indicates 
  671. how and where the caller would like to have the text formatted.
  672.  
  673.  ----*/    
  674. int
  675. format_message(msgno, env, body, flgs, pc)
  676.     long         msgno;
  677.     ENVELOPE    *env;
  678.     BODY        *body;
  679.     int          flgs;
  680.     gf_io_t      pc;
  681. {
  682.     char     *decode_error;
  683.     HEADER_S  h;
  684.     ATTACH_S *a;
  685.     int       show_parts, error_found = 0, i;
  686.     gf_io_t   gc;
  687.  
  688.  
  689.     show_parts = !(flgs&FM_DO_PRINT);
  690.  
  691.     /*---- format and copy envelope ----*/
  692.     if(ps_global->full_header)
  693.       q_status_message(SM_INFO, 0, 3,
  694.                "Full header mode ON.  All header text being included");
  695.  
  696.     HD_INIT(&h, ps_global->VAR_VIEW_HEADERS, ps_global->view_all_except,
  697.         FE_DEFAULT);
  698.     switch(format_header_text(ps_global->mail_stream, msgno, env, &h,pc,NULL)){
  699.                   
  700.       case -1 :            /* write error */
  701.     goto write_error;
  702.  
  703.       case 1 :            /* fetch error */
  704.     if(!(gf_puts("[ Error fetching header ]",  pc)
  705.          && !gf_puts(NEWLINE, pc)))
  706.       goto write_error;
  707.  
  708.     break;
  709.     }
  710.  
  711.     if(body == NULL) {
  712.         /*--- Server is not an IMAP2bis, It can't parse MIME
  713.               so we just show the text here. Hopefully the 
  714.               message isn't a MIME message 
  715.           ---*/
  716.     void *text2;
  717. #if    defined(DOS) && !defined(WIN32)
  718.     char *append_file_name;
  719.  
  720.     /* for now, always fetch to disk.  This could be tuned to
  721.      * check for message size, then decide to deal with it on disk...
  722.      */
  723.     mail_parameters(ps_global->mail_stream, SET_GETS, (void *)dos_gets);
  724.     if(!(append_file_name = temp_nam(NULL, "pv"))
  725.        || !(append_file = fopen(append_file_name, "w+b"))){
  726.         if(append_file_name)
  727.           fs_give((void **)&append_file_name);
  728.  
  729.         q_status_message1(SM_ORDER,3,3,"Can't make temp file: %s",
  730.                   error_description(errno));
  731.         return(0);
  732.     }
  733. #endif
  734.  
  735.         if(text2 = (void *)mail_fetchtext(ps_global->mail_stream, msgno)){
  736.          if(!gf_puts(NEWLINE, pc))        /* write delimiter */
  737.           goto write_error;
  738. #if    defined(DOS) && !defined(WIN32)
  739.         gf_set_readc(&gc, append_file, 0L, FileStar);
  740. #else
  741.         gf_set_readc(&gc, text2, (unsigned long)strlen(text2), CharStar);
  742. #endif
  743.         gf_filter_init();
  744.         gf_link_filter(gf_nvtnl_local);
  745.         if(decode_error = gf_pipe(gc, pc)){
  746.                 sprintf(tmp_20k_buf, "%s%s    [Formatting error: %s]%s",
  747.             NEWLINE, NEWLINE, decode_error, NEWLINE);
  748.         if(!gf_puts(tmp_20k_buf, pc))
  749.           goto write_error;
  750.         }
  751.     }
  752.  
  753. #if    defined(DOS) && !defined(WIN32)
  754.     fclose(append_file);            /* clean up tmp file */
  755.     append_file = NULL;
  756.     unlink(append_file_name);
  757.     fs_give((void **)&append_file_name);
  758.     mail_gc(ps_global->mail_stream, GC_TEXTS);
  759.     mail_parameters(ps_global->mail_stream, SET_GETS, (void *)NULL);
  760. #endif
  761.  
  762.     if(!text2){
  763.         if(!gf_puts(NEWLINE, pc)
  764.            || !gf_puts("    [ERROR fetching text of message]", pc)
  765.            || !gf_puts(NEWLINE, pc)
  766.            || !gf_puts(NEWLINE, pc))
  767.           goto write_error;
  768.     }
  769.  
  770.     return(1);
  771.     }
  772.  
  773.     if(flgs&FM_NEW_MESS) {
  774.         zero_atmts(ps_global->atmts);
  775.         describe_mime(body, "", 1, 1);
  776.     }
  777.  
  778.     /*---- Calculate the approximate length of what we've got to show  ---*/
  779.  
  780.     /*=========== Format the header into the buffer =========*/
  781.     /*----- First do the list of parts/attachments if needed ----*/
  782.     if(show_parts && ps_global->atmts[1].description != NULL) {
  783.     int max_num_l = 0, max_size_l = 0;
  784.  
  785.     if(!gf_puts("Parts/attachments:", pc) || !gf_puts(NEWLINE, pc))
  786.       goto write_error;
  787.  
  788.         /*----- Figure display lengths for nice display -----*/
  789.         for(a = ps_global->atmts; a->description != NULL; a++) {
  790.         if(strlen(a->number) > max_num_l)
  791.           max_num_l = strlen(a->number);
  792.         if(strlen(a->size) > max_size_l)
  793.           max_size_l = strlen(a->size);
  794.     }
  795.  
  796.         /*----- Format the list of attachments -----*/
  797.         for(a = ps_global->atmts; a->description != NULL; a++) {
  798.         int i = ps_global->ttyo->screen_cols - max_num_l - max_size_l
  799.              - 14 - strlen(NEWLINE);
  800.             sprintf(tmp_20k_buf, "   %-*.*s %s  %*.*s  %-*.*s%s",
  801.                     max_num_l, max_num_l, a->number,
  802.                     (a->shown ? "Shown" : (a->can_display != CD_NOCANDO)
  803.                         ? "  OK " : "     "),
  804.                     max_size_l, max_size_l, a->size, i, i, a->description,
  805.             NEWLINE);
  806.         if(!gf_puts(tmp_20k_buf, pc))
  807.           goto write_error;
  808.         }
  809.  
  810.     if(!gf_puts("----------------------------------------", pc)
  811.        || !gf_puts(NEWLINE, pc))
  812.       goto write_error;
  813.     }
  814.  
  815.     if(!gf_puts(NEWLINE, pc))        /* write delimiter */
  816.       goto write_error;
  817.  
  818.     show_parts = 0;
  819.  
  820.     /*======== Now loop through formatting all the parts =======*/
  821.     for(a = ps_global->atmts; a->description != NULL; a++) {
  822.  
  823.         if(!a->shown) {
  824.         if(!gf_puts(part_desc(a->number, a->body,
  825.                   (flgs&FM_DO_PRINT)
  826.                     ? 3
  827.                     : (a->can_display != CD_NOCANDO) ? 1 : 2),
  828.             pc)
  829.            || ! gf_puts(NEWLINE, pc))
  830.           goto write_error;
  831.  
  832.             continue;
  833.         } 
  834.  
  835.         switch(a->body->type){
  836.  
  837.           case TYPETEXT:
  838.         /*
  839.          * Don't write our delimiter if this text part is
  840.          * the first part of a message/rfc822 segment...
  841.          */
  842.         if(show_parts && a != ps_global->atmts 
  843.            && a[-1].body && a[-1].body->type != TYPEMESSAGE){
  844.         sprintf(tmp_20k_buf, "%s  [ Part %s: \"%.55s\" ]%s%s",
  845.             NEWLINE, a->number, 
  846.             a->body->description ? a->body->description
  847.                          : "Attached Text",
  848.             NEWLINE, NEWLINE);
  849.         if(!gf_puts(tmp_20k_buf, pc))
  850.           goto write_error;
  851.         }
  852.  
  853.         error_found += decode_text(a, msgno, pc,
  854.                        (flgs&FM_DO_PRINT) ? QStatus : InLine,
  855.                        !(flgs&FM_DO_PRINT));
  856.             break;
  857.  
  858.           case TYPEMESSAGE:
  859.             sprintf(tmp_20k_buf, "%s  [ Part %s: \"%.55s\" ]%s%s",
  860.             NEWLINE, a->number, 
  861.                     a->body->description ? a->body->description
  862.                      : "Included Message",
  863.             NEWLINE, NEWLINE);
  864.           if(!gf_puts(tmp_20k_buf, pc))
  865.           goto write_error;
  866.  
  867.         if(a->body->subtype && strucmp(a->body->subtype, "rfc822") == 0){
  868.         format_envelope(NULL, 0L, a->body->contents.msg.env, pc,
  869.                 FE_DEFAULT, NULL);
  870.         }
  871.             else if(a->body->subtype 
  872.             && strucmp(a->body->subtype, "external-body") == 0) {
  873.         if(!gf_puts("This part is not included and can be ", pc)
  874.            || !gf_puts("fetched as follows:", pc)
  875.            || !gf_puts(NEWLINE, pc)
  876.            || !gf_puts(display_parameters(a->body->parameter), pc))
  877.           goto write_error;
  878.             }
  879.         else
  880.           error_found += decode_text(a, msgno, pc, 
  881.                      (flgs&FM_DO_PRINT) ? QStatus : InLine,
  882.                      !(flgs&FM_DO_PRINT));
  883.  
  884.         if(!gf_puts(NEWLINE, pc))
  885.           goto write_error;
  886.  
  887.             break;
  888.  
  889.           default:
  890.         if(!gf_puts(part_desc(a->number,a->body,(flgs&FM_DO_PRINT) ? 3:1),
  891.             pc))
  892.           goto write_error;
  893.         }
  894.  
  895.     show_parts++;
  896.     }
  897.  
  898.     return(!error_found);
  899.  
  900.   write_error:
  901.  
  902.     if(flgs & FM_DO_PRINT)
  903.       q_status_message1(SM_ORDER, 3, 4, "Error writing message: %s", 
  904.             error_description(errno));
  905.     return(0);
  906. }
  907.  
  908.  
  909. /*
  910.  *  This is a list of header fields that are represented canonically
  911.  *  by the c-client's ENVELOPE structure.  The list is used by the
  912.  *  two functions below to decide if a given field is included in this
  913.  *  set.
  914.  */
  915. static struct envelope_s {
  916.     char *name;
  917.     long val;
  918. } envelope_hdrs[] = {
  919.     {"from",        FE_FROM},
  920.     {"sender",        FE_SENDER},
  921.     {"date",        FE_DATE},
  922.     {"to",        FE_TO},
  923.     {"cc",        FE_CC},
  924.     {"bcc",        FE_BCC},
  925.     {"newsgroups",    FE_NEWSGROUPS},
  926.     {"subject",        FE_SUBJECT},
  927.     {"message-id",    FE_MESSAGEID},
  928.     {"reply-to",    FE_REPLYTO},
  929.     {"in-reply-to",    FE_INREPLYTO},
  930.     {NULL,        0}
  931. };
  932.  
  933.  
  934.  
  935. /*
  936.  * is_an_env_hdr - is this name a header in the envelope structure?
  937.  *
  938.  *             name - the header name to check
  939.  * news_is_env_hack - Hack to compensate for c-client deficiency.  Newsgroups
  940.  *                    are normally part of the envelope, unless it is over
  941.  *                    imap, in which case c-client fails to fill it in for us.
  942.  */
  943. int
  944. is_an_env_hdr(name, news_is_env_hack)
  945. char *name;
  946. int   news_is_env_hack;
  947. {
  948.     register int i;
  949.  
  950.     if(!news_is_env_hack && !strucmp(name, "newsgroups"))
  951.       return(0);
  952.  
  953.     for(i = 0; envelope_hdrs[i].name; i++)
  954.       if(!strucmp(name, envelope_hdrs[i].name))
  955.     return(1);
  956.  
  957.     return(0);
  958. }
  959.  
  960.  
  961.  
  962. /*
  963.  * Format a single field from the envelope
  964.  */
  965. void
  966. format_env_hdr(stream, msgno, env, pc, field, prefix)
  967.     MAILSTREAM *stream;
  968.     long    msgno;
  969.     ENVELOPE   *env;
  970.     gf_io_t    pc;
  971.     char       *field;
  972.     char       *prefix;
  973. {
  974.     register int i;
  975.  
  976.     for(i = 0; envelope_hdrs[i].name; i++)
  977.       if(!strucmp(field, envelope_hdrs[i].name)){
  978.       format_envelope(stream, msgno, env, pc, envelope_hdrs[i].val,prefix);
  979.       return;
  980.       }
  981. }
  982.  
  983.  
  984. /*
  985.  * Look through header string headers, beginning with begin, for the next
  986.  * occurrence of header field.  Set start to that.  Set end to point one
  987.  * position past all of the continuation lines that go with field.
  988.  * That is, if end is converted to a null
  989.  * character then the string start will be the next occurence of header
  990.  * field including all of its continuation lines.  Headers is assumed to
  991.  * have CRLF's as end of lines.
  992.  *
  993.  * If field is NULL, then we just leave start pointing to begin and
  994.  * make end the end of that header.
  995.  *
  996.  * Returns 1 if found, 0 if not.
  997.  */
  998. int
  999. delineate_this_header(headers, field, begin, start, end)
  1000.     char  *headers, *field, *begin;
  1001.     char **start, **end;
  1002. {
  1003.     char tmpfield[MAILTMPLEN+2]; /* copy of field with colon appended */
  1004.     char *p;
  1005.  
  1006.     if(field == NULL){
  1007.     if(!begin || !*begin || isspace(*begin))
  1008.       return 0;
  1009.     else
  1010.       *start = begin;
  1011.     }
  1012.     else{
  1013.     strncpy(tmpfield, field, MAILTMPLEN);
  1014.     tmpfield[MAILTMPLEN] = '\0';
  1015.     strcat(tmpfield, ":");
  1016.  
  1017.     *start = srchstr(begin, tmpfield);
  1018.     if(!*start)
  1019.       return 0;
  1020.     }
  1021.  
  1022.     for(p = *start; *p; p++){
  1023.     if(ISRFCEOL(p) && (!isspace(*(p+2)) || *(p+2) == '\015')){
  1024.         /*
  1025.          * The final 015 in the test above is to test for the end
  1026.          * of the headers.
  1027.          */
  1028.         *end = p+2;
  1029.         break;
  1030.     }
  1031.     }
  1032.  
  1033.     if(!*p)
  1034.       *end = p;
  1035.     
  1036.     return 1;
  1037. }
  1038.  
  1039.  
  1040.  
  1041. /*----------------------------------------------------------------------
  1042.    Handle fetching and filtering a text message segment to be displayed
  1043.    by scrolltool or printed.
  1044.  
  1045. Args: att   -- segment to fetch
  1046.       msgno -- message number segment is a part of
  1047.       pc    -- function to write characters from segment with
  1048.       style -- Indicates special handling for error messages
  1049.       display_bound -- Indicates special necessary filtering
  1050.  
  1051. Returns: 1 if errors encountered, 0 if everything went A-OK
  1052.  
  1053.  ----*/     
  1054. int
  1055. decode_text(att, msgno, pc, style, display_bound)
  1056.     ATTACH_S       *att;
  1057.     long            msgno;
  1058.     gf_io_t         pc;
  1059.     DetachErrStyle  style;
  1060.     int            display_bound;
  1061. {
  1062.     PARAMETER         *param;
  1063.     filter_t           aux_filter[5];
  1064.     int               filtcnt = 0, error_found = 0;
  1065.     char          *err;
  1066.  
  1067.     if(F_OFF(F_PASS_CONTROL_CHARS, ps_global)){
  1068.     aux_filter[filtcnt++] = gf_escape_filter;
  1069.     aux_filter[filtcnt++] = gf_control_filter;
  1070.     }
  1071.  
  1072.     if(att->body->subtype){
  1073.     filter_t rt_filt = NULL;
  1074.  
  1075.     if(!strucmp(att->body->subtype, "richtext")){
  1076.         gf_rich2plain_opt(!display_bound);    /* maybe strip everything! */
  1077.         rt_filt = gf_rich2plain;
  1078.     }
  1079.     else if(!strucmp(att->body->subtype, "enriched")){
  1080.         gf_enriched2plain_opt(!display_bound);
  1081.         rt_filt = gf_enriched2plain;
  1082.     }
  1083.  
  1084.     if(rt_filt){
  1085.         aux_filter[filtcnt++] = rt_filt;
  1086.         if(!display_bound){
  1087.         gf_wrap_filter_opt(75);  /* width to use for file or printer */
  1088.         aux_filter[filtcnt++] = gf_wrap;
  1089.         }
  1090.     }
  1091.     else if(!strucmp(att->body->subtype, "html")
  1092.         && !ps_global->full_header){
  1093.         
  1094. /*BUG:        sniff the params for "version=2.0" ala draft-ietf-html-spec-01 */
  1095. /*        aux_filter[filtcnt++] = gf_html2plain;*/
  1096.     }
  1097.     }
  1098.  
  1099.     for(param = att->body->parameter; 
  1100.         param != NULL && strucmp(param->attribute,"charset") != 0;
  1101.         param = param->next)
  1102.     ;
  1103.  
  1104.     /*
  1105.      * If there's a charset specified and it's not US-ASCII, and our
  1106.      * local charset's undefined or it's not the same as the specified
  1107.      * charset, put up a warning...
  1108.      */
  1109.     if(param && param->value && strucmp(param->value, "us-ascii")
  1110.        && (!ps_global->VAR_CHAR_SET
  1111.        || strucmp(param->value, ps_global->VAR_CHAR_SET)))
  1112.       if(!gf_puts("    [The following text is in the \"", pc)
  1113.      || !gf_puts(param->value, pc)
  1114.      || !gf_puts("\" character set]", pc)
  1115.      || !gf_puts(NEWLINE , pc)
  1116.      || !gf_puts("    [Your display is set for the \"" , pc)
  1117.      || !gf_puts(ps_global->VAR_CHAR_SET
  1118.                ? ps_global->VAR_CHAR_SET : "US-ASCII" , pc)
  1119.      || !gf_puts("\" character set]" , pc)
  1120.      || !gf_puts(NEWLINE , pc)
  1121.      || !gf_puts("    [Some characters may be displayed incorrectly]",pc)
  1122.      || !gf_puts(NEWLINE, pc)
  1123.      || !gf_puts(NEWLINE, pc))
  1124.     goto write_error;
  1125.  
  1126.     aux_filter[filtcnt] = NULL;
  1127.     err = detach(ps_global->mail_stream, msgno, att->body, att->number,
  1128.          NULL, pc, aux_filter);
  1129.     if(err) {
  1130.     error_found++;
  1131.     if(style == QStatus) {
  1132.         q_status_message1(SM_ORDER, 3, 4, "%s", err);
  1133.     } else if(style == InLine) {
  1134.         sprintf(tmp_20k_buf, "%s   [Error: %s]  %c%s%c%s%s",
  1135.             NEWLINE, err,
  1136.             att->body->description ? '\"' : ' ',
  1137.             att->body->description ? att->body->description : "",
  1138.             att->body->description ? '\"' : ' ',
  1139.             NEWLINE, NEWLINE);
  1140.         if(!gf_puts(tmp_20k_buf, pc))
  1141.           goto write_error;
  1142.     }
  1143.     }
  1144.  
  1145.     if(att->body->subtype
  1146.        && (!strucmp(att->body->subtype, "richtext")
  1147.        || !strucmp(att->body->subtype, "enriched"))
  1148.        && !display_bound){
  1149.     if(!gf_puts(NEWLINE, pc) || !gf_puts(NEWLINE, pc))
  1150.       goto write_error;
  1151.     }
  1152.  
  1153.     return(error_found);
  1154.  
  1155.   write_error:
  1156.     if(style == QStatus)
  1157.       q_status_message1(SM_ORDER, 3, 4, "Error writing message: %s", 
  1158.             error_description(errno));
  1159.  
  1160.     return(1);
  1161. }
  1162.  
  1163.  
  1164.  
  1165.  
  1166.  
  1167. /*------------------------------------------------------------------
  1168.    This list of known escape sequences is taken from RFC's 1486 and 1554
  1169.    and draft-apng-cc-encoding, and the X11R5 source with only a remote
  1170.    understanding of what this all means...
  1171.  
  1172.    NOTE: if the length of these should extend beyond 4 chars, fix
  1173.      MAX_ESC_LEN in filter.c
  1174.   ----*/
  1175. static char *known_escapes[] = {
  1176.     "(B",  "(J",  "$@",  "$B",            /* RFC 1468 */
  1177.     "$A",  "$(C", "$(D", ".A",  ".F",        /* added by RFC 1554 */
  1178.     "$)C", "$)A", "$*E", "$*X",            /* those in apng-draft */
  1179.     "$+G", "$+H", "$+I", "$+J", "$+K",
  1180.     "$+L", "$+M",
  1181.     ")I",   "-A",  "-B",  "-C",  "-D",        /* codes form X11R5 source */
  1182.     "-F",   "-G",  "-H",   "-L",  "-M",
  1183.     "-$(A", "$(B", "$)B", "$)D",
  1184.     NULL};
  1185.  
  1186. int
  1187. match_escapes(esc_seq)
  1188.     char *esc_seq;
  1189. {
  1190.     char **p;
  1191.     int    n;
  1192.  
  1193.     for(p = known_escapes; *p && strncmp(esc_seq, *p, n = strlen(*p)); p++)
  1194.       ;
  1195.  
  1196.     return(*p ? n + 1 : 0);
  1197. }
  1198.  
  1199.  
  1200.  
  1201. /*----------------------------------------------------------------------
  1202.   Format header text suitable for display
  1203.  
  1204.   Args: stream -- mail stream for various header text fetches
  1205.     msgno -- sequence number in stream of message we're interested in
  1206.     env -- pointer to msg's envelope
  1207.     hdrs -- struct containing what's to get formatted
  1208.     pc -- function to write header text with
  1209.     prefix -- prefix to append to each output line
  1210.  
  1211.   Result: 0 if all's well, -1 if write error, 1 if fetch error
  1212.  
  1213.   NOTE: Blank-line delimiter is NOT written here.  Newlines are written
  1214.     in the local convention.
  1215.  
  1216.  ----*/
  1217. #define    FHT_OK        0
  1218. #define    FHT_WRTERR    -1
  1219. #define    FHT_FTCHERR    1
  1220. int
  1221. format_header_text(stream, msgno, env, hdrs, pc, prefix)
  1222.     MAILSTREAM *stream;
  1223.     long    msgno;
  1224.     ENVELOPE   *env;
  1225.     HEADER_S   *hdrs;
  1226.     gf_io_t    pc;
  1227.     char       *prefix;
  1228. {
  1229.     int   rv = FHT_OK;
  1230.     int  nfields, i;
  1231.     char *h = NULL, **fields = NULL, **v, *p, *q, *start,
  1232.      *finish, *current;
  1233.  
  1234.     if(ps_global->full_header)
  1235.       return(format_raw_header(stream, msgno, pc, prefix));
  1236.  
  1237.     /*
  1238.      * First, calculate how big a fields array we need.
  1239.      */
  1240.  
  1241.     /* Custom header viewing list specified */
  1242.     if(hdrs->type == HD_LIST){
  1243.     /* view all these headers */
  1244.     if(!hdrs->except){
  1245.         for(nfields = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
  1246.           if(!is_an_env_hdr(q, env && env->newsgroups))
  1247.         nfields++;
  1248.     }
  1249.     /* view all except these headers */
  1250.     else{
  1251.         for(nfields = 0, v = hdrs->h.l; *v != NULL; v++)
  1252.           nfields++;
  1253.           
  1254.         if(nfields > 1)
  1255.           nfields--; /* subtract one for ALL_EXCEPT field */
  1256.     }
  1257.     }
  1258.     /* default view */
  1259.     else{
  1260.     nfields = 6;
  1261.     if(!(env && env->newsgroups))
  1262.       nfields++;
  1263.     }
  1264.  
  1265.     /* allocate pointer space */
  1266.     if(nfields){
  1267.     fields = (char **)fs_get((size_t)(nfields+1) * sizeof(char *));
  1268.     memset(fields, 0, (size_t)(nfields+1) * sizeof(char *));
  1269.     }
  1270.  
  1271.     if(hdrs->type == HD_LIST){
  1272.     /* view all these headers */
  1273.     if(!hdrs->except){
  1274.         /* put the non-envelope headers in fields */
  1275.         if(nfields)
  1276.           for(i = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
  1277.         if(!is_an_env_hdr(q, env && env->newsgroups))
  1278.           fields[i++] = q;
  1279.     }
  1280.     /* view all except these headers */
  1281.     else{
  1282.         /* put the list of headers not to view in fields */
  1283.         if(nfields)
  1284.           for(i = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
  1285.         if(strucmp(ALL_EXCEPT, q))
  1286.           fields[i++] = q;
  1287.     }
  1288.  
  1289.     v = hdrs->h.l;
  1290.     }
  1291.     else{
  1292.     if(nfields){
  1293.         i = 0;
  1294.         if(!(env && env->newsgroups))
  1295.           fields[i++] = "Newsgroups";
  1296.  
  1297.         fields[i++] = "Resent-Date";
  1298.         fields[i++] = "Resent-From";
  1299.         fields[i++] = "Resent-To";
  1300.         fields[i++] = "Resent-cc";
  1301.         fields[i++] = "Resent-Subject";
  1302.         fields[i++] = "Followup-To";
  1303.     }
  1304.  
  1305.     v = fields;
  1306.     }
  1307.  
  1308.     /* custom view with exception list */
  1309.     if(hdrs->type == HD_LIST && hdrs->except){
  1310.     /*
  1311.      * Go through each header in h and print it.
  1312.      * First we check to see if it is an envelope header so we
  1313.      * can print our envelope version of it instead of the raw version.
  1314.      */
  1315.  
  1316.     /* fetch all the other headers */
  1317.     if(nfields)
  1318.       h = xmail_fetchheader_lines_not(stream, msgno, fields);
  1319.  
  1320.     for(current = h;
  1321.         h && delineate_this_header(h,NULL,current,&start,&finish);
  1322.         current = finish){
  1323.         char tmp[MAILTMPLEN+1];
  1324.         char *colon_loc;
  1325.  
  1326.         colon_loc = strindex(start, ':');
  1327.         if(colon_loc && colon_loc < finish){
  1328.         strncpy(tmp, start, min(colon_loc-start, MAILTMPLEN));
  1329.         tmp[min(colon_loc-start, MAILTMPLEN)] = '\0';
  1330.         }
  1331.         else
  1332.           colon_loc = NULL;
  1333.  
  1334.         if(colon_loc && is_an_env_hdr(tmp, env && env->newsgroups)){
  1335.         /* pretty format for env hdrs */
  1336.         format_env_hdr(stream, msgno, env, pc, tmp, prefix);
  1337.         }
  1338.         else{
  1339.         if(rv = format_raw_hdr_string(start, finish, pc, prefix))
  1340.           goto write_error;
  1341.         else
  1342.           start = finish;
  1343.         }
  1344.     }
  1345.     }
  1346.     /* custom view or default */
  1347.     else{
  1348.     /* fetch the non-envelope headers */
  1349.     if(nfields)
  1350.       h = xmail_fetchheader_lines(stream, msgno, fields);
  1351.  
  1352.     /* default envelope for default view */
  1353.     if(hdrs->type == HD_BFIELD)
  1354.       format_envelope(stream, msgno, env, pc, hdrs->h.b, prefix);
  1355.  
  1356.     /* go through each header in list, v initialized above */
  1357.     for(; q = *v; v++){
  1358.         if(is_an_env_hdr(q, env && env->newsgroups)){
  1359.         /* pretty format for env hdrs */
  1360.         format_env_hdr(stream, msgno, env, pc, q, prefix);
  1361.         }
  1362.         else{
  1363.         /*
  1364.          * Go through h finding all occurences of this header
  1365.          * and all continuation lines, and output.
  1366.          */
  1367.         for(current = h;
  1368.             h && delineate_this_header(h,q,current,&start,&finish);
  1369.             current = finish){
  1370.             if(rv = format_raw_hdr_string(start, finish, pc, prefix))
  1371.               goto write_error;
  1372.             else
  1373.               start = finish;
  1374.         }
  1375.         }
  1376.     }
  1377.     }
  1378.  
  1379.   write_error:
  1380.  
  1381.     if(h)
  1382.       fs_give((void **)&h);
  1383.  
  1384.     if(fields)
  1385.       fs_give((void **)&fields);
  1386.  
  1387.     return(rv);
  1388. }
  1389.  
  1390.  
  1391.  
  1392. /*----------------------------------------------------------------------
  1393.   Format RAW header text for display
  1394.  
  1395.   Args: stream --
  1396.     msgno --
  1397.     pc --
  1398.     prefix --
  1399.  
  1400.   Result: 0 if all's well, -1 if write error, 1 if fetch error
  1401.  
  1402.   NOTE: Blank-line delimiter is NOT written here.  Newlines are written
  1403.     in the local convention.
  1404.  
  1405.  ----*/
  1406. int
  1407. format_raw_header(stream, msgno, pc, prefix)
  1408.     MAILSTREAM *stream;
  1409.     long    msgno;
  1410.     gf_io_t    pc;
  1411.     char       *prefix;
  1412. {
  1413.     char *h = mail_fetchheader(stream, msgno);
  1414.  
  1415.     if(h){
  1416.     if(prefix && !gf_puts(prefix, pc))
  1417.       return(FHT_WRTERR);
  1418.  
  1419.     while(*h){
  1420.         if(ISRFCEOL(h)){
  1421.         h += 2;
  1422.         if(!gf_puts(NEWLINE, pc))
  1423.           return(FHT_WRTERR);
  1424.  
  1425.         if(ISRFCEOL(h))        /* all done! */
  1426.           return(FHT_OK);
  1427.  
  1428.         if(prefix && !gf_puts(prefix, pc))
  1429.           return(FHT_WRTERR);
  1430.         }
  1431.         else if(!(*pc)(*h++))
  1432.           return(FHT_WRTERR);
  1433.     }
  1434.     }
  1435.     else
  1436.       return(FHT_FTCHERR);
  1437.  
  1438.     return(FHT_OK);
  1439. }
  1440.  
  1441.  
  1442.  
  1443. /*----------------------------------------------------------------------
  1444.   Format c-client envelope data suitable for display
  1445.  
  1446.   Args: stream -- stream associated with this envelope
  1447.     msgno -- message number associated with this envelope
  1448.     e -- envelope
  1449.     pc -- place to write result
  1450.     which -- which header lines to write
  1451.     prefix -- string to write before each header line
  1452.  
  1453.   Result: 0 if all's well, -1 if write error, 1 if fetch error
  1454.  
  1455.   NOTE: Blank-line delimiter is NOT written here.  Newlines are written
  1456.     in the local convention.
  1457.  
  1458.  ----*/
  1459. void
  1460. format_envelope(s, n, e, pc, which, prefix)
  1461.     MAILSTREAM *s;
  1462.     long    n;
  1463.     ENVELOPE   *e;
  1464.     gf_io_t     pc;
  1465.     long    which;
  1466.     char       *prefix;
  1467. {
  1468.     if(!e)
  1469.       return;
  1470.  
  1471.     if((which & FE_DATE) && e->date) {
  1472.     if(prefix)
  1473.       gf_puts(prefix, pc);
  1474.  
  1475.     gf_puts("Date: ", pc);
  1476.     gf_puts(e->date, pc);
  1477.     gf_puts(NEWLINE, pc);
  1478.     }
  1479.  
  1480.     if((which & FE_FROM) && e->from)
  1481.       format_addr_string(s, n, "From: ", e->from, prefix, pc);
  1482.  
  1483.     if((which & FE_REPLYTO) && e->reply_to
  1484.        && (!e->from || !address_is_same(e->reply_to, e->from)))
  1485.       format_addr_string(s, n, "Reply-To: ", e->reply_to, prefix, pc);
  1486.  
  1487.     if((which & FE_TO) && e->to)
  1488.       format_addr_string(s, n, "To: ", e->to, prefix, pc);
  1489.  
  1490.     if((which & FE_CC) && e->cc)
  1491.       format_addr_string(s, n, "Cc: ", e->cc, prefix, pc);
  1492.  
  1493.     if((which & FE_BCC) && e->bcc)
  1494.       format_addr_string(s, n, "Bcc: ", e->bcc, prefix, pc);
  1495.  
  1496.     if((which & FE_NEWSGROUPS) && e->newsgroups && !ps_global->nr_mode)
  1497.       format_newsgroup_string("Newsgroups: ", e->newsgroups, prefix, pc);
  1498.  
  1499.     if((which & FE_SUBJECT) && e->subject && e->subject[0]){
  1500.     if(prefix)
  1501.       gf_puts(prefix, pc);
  1502.  
  1503.     gf_puts("Subject: ", pc);
  1504.     gf_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
  1505.                     e->subject, NULL), pc);
  1506.     gf_puts(NEWLINE, pc);
  1507.     }
  1508.  
  1509.     if((which & FE_SENDER) && e->sender
  1510.        && (!e->from || !address_is_same(e->sender, e->from)))
  1511.       format_addr_string(s, n, "Sender: ", e->sender, prefix, pc);
  1512.  
  1513.     if((which & FE_MESSAGEID) && e->message_id){
  1514.     if(prefix)
  1515.       gf_puts(prefix, pc);
  1516.  
  1517.     gf_puts("Message-ID: ", pc);
  1518.     gf_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
  1519.                     e->message_id, NULL), pc);
  1520.     gf_puts(NEWLINE, pc);
  1521.     }
  1522.  
  1523.     if((which & FE_INREPLYTO) && e->in_reply_to){
  1524.     if(prefix)
  1525.       gf_puts(prefix, pc);
  1526.  
  1527.     gf_puts("In-Reply-To: ", pc);
  1528.     gf_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
  1529.                     e->in_reply_to, NULL), pc);
  1530.     gf_puts(NEWLINE, pc);
  1531.     }
  1532. }
  1533.  
  1534.  
  1535.  
  1536. /*----------------------------------------------------------------------
  1537.      Format an address field, wrapping lines nicely at commas
  1538.  
  1539.   Args: field_name  -- The name of the field we're formatting ("TO: ", ...)
  1540.         addr        -- ADDRESS structure to format
  1541.         line_prefix -- A prefix string for each line such as "> "
  1542.  
  1543.  Result: A formatted, malloced string is returned.
  1544.  
  1545. The resulting lines formatted are 80 columns wide.
  1546.   ----------------------------------------------------------------------*/
  1547. void
  1548. format_addr_string(stream, msgno, field_name, addr, line_prefix, pc)
  1549.     MAILSTREAM *stream;
  1550.     long    msgno;
  1551.     ADDRESS    *addr;
  1552.     char       *line_prefix, *field_name;
  1553.     gf_io_t    pc;
  1554. {
  1555.     char     buf[MAILTMPLEN], *ptmp;
  1556.     int         trailing = 0, was_start_of_group = 0, llen, alen, plen = 0;
  1557.     ADDRESS *atmp;
  1558.  
  1559.     if(!addr)
  1560.       return;
  1561.  
  1562.     if(line_prefix)
  1563.       gf_puts(line_prefix, pc);
  1564.  
  1565.     /*
  1566.      * quickly run down address list to make sure none are patently bogus.
  1567.      * If so, just blat raw field out.
  1568.      */
  1569.     for(atmp = addr; stream && atmp; atmp = atmp->next)
  1570.       if(atmp->host && atmp->host[0] == '.'){
  1571.       char *field, *fields[2];
  1572.  
  1573.       fields[1] = NULL;
  1574.       fields[0] = cpystr(field_name);
  1575.       if(ptmp = strchr(fields[0], ':'))
  1576.         *ptmp = '\0';
  1577.  
  1578.       if(field = xmail_fetchheader_lines(stream, msgno, fields)){
  1579.           char *h, *t;
  1580.  
  1581.           for(t = h = field; *h ; t++)
  1582.         if(*t == '\015' && *(t+1) == '\012'){
  1583.             *t = '\0';            /* tie off line */
  1584.             gf_puts(h, pc);
  1585.             if(*(h = (++t) + 1)){    /* set new h and skip CRLF */
  1586.             gf_puts(NEWLINE, pc);    /* more to write */
  1587.             if(line_prefix)
  1588.               gf_puts(line_prefix, pc);
  1589.             }
  1590.             else
  1591.               break;
  1592.         }
  1593.         else if(!*t){            /* shouldn't happen much */
  1594.             if(h != t)
  1595.               gf_puts(h, pc);
  1596.  
  1597.             break;
  1598.         }
  1599.  
  1600.           fs_give((void **)&field);
  1601.       }
  1602.  
  1603.       fs_give((void **)&fields[0]);
  1604.       gf_puts(NEWLINE, pc);
  1605.       q_status_message1(SM_ORDER, 0, 3, "Error in \"%s\" field address",
  1606.                 field_name);
  1607.       return;
  1608.       }
  1609.  
  1610.     gf_puts(field_name, pc);
  1611.  
  1612.     if(line_prefix)
  1613.       plen = strlen(line_prefix);
  1614.  
  1615.     llen = plen + strlen(field_name);
  1616.     while(addr){
  1617.     atmp           = addr->next;        /* remember what's next */
  1618.     addr->next     = NULL;
  1619.     ptmp           = addr->personal;    /* RFC 1522 personal name? */
  1620.     addr->personal = (char *) rfc1522_decode((unsigned char *)tmp_20k_buf,
  1621.                          addr->personal, NULL);
  1622.     buf[0]           = '\0';
  1623.     rfc822_write_address(buf, addr);    /* write address into buf */
  1624.     alen           = strlen(buf);
  1625.     addr->personal = ptmp;            /* restore old personal ptr */
  1626.  
  1627.     if(!trailing){                /* 1st pass, just address */
  1628.         llen += alen;
  1629.         trailing++;
  1630.     }
  1631.     else{                    /* else comma, unless */
  1632.         if(!((was_start_of_group && addr->host)  /* 1st addr in group, */
  1633.            || (!addr->host && !addr->mailbox))){ /* or end of group */
  1634.         gf_puts(",", pc);
  1635.         llen++;
  1636.         }
  1637.  
  1638.         if(alen + llen + 1 > 76){
  1639.         gf_puts(NEWLINE, pc);
  1640.         if(line_prefix)
  1641.           gf_puts(line_prefix, pc);
  1642.  
  1643.         gf_puts("    ", pc);
  1644.         llen = alen + plen + 5;
  1645.         }
  1646.         else{
  1647.         gf_puts(" ", pc);
  1648.         llen += alen + 1;
  1649.         }
  1650.     }
  1651.  
  1652.     if(alen && llen > 76){        /* handle long addresses */
  1653.         register char *q, *p = &buf[alen-1];
  1654.  
  1655.         while(p > buf){
  1656.         if(isspace(*p) && (llen - (alen - (int)(p - buf))) < 76){
  1657.             for(q = buf; q < p; q++)
  1658.               (*pc)(*q);    /* write character */
  1659.  
  1660.             gf_puts(NEWLINE, pc);
  1661.             gf_puts("    ", pc);
  1662.             gf_puts(p, pc);
  1663.             break;
  1664.         }
  1665.         else
  1666.           p--;
  1667.         }
  1668.  
  1669.         if(p == buf)        /* no reasonable break point */
  1670.           gf_puts(buf, pc);
  1671.     }
  1672.     else
  1673.       gf_puts(buf, pc);
  1674.  
  1675.     if(!addr->host && addr->mailbox)
  1676.       was_start_of_group = 1;
  1677.     else
  1678.       was_start_of_group = 0;
  1679.  
  1680.     addr->next = atmp;
  1681.     addr       = atmp;
  1682.     }
  1683.  
  1684.     gf_puts(NEWLINE, pc);
  1685. }
  1686.  
  1687.  
  1688.  
  1689. /*----------------------------------------------------------------------
  1690.   Format an address field, wrapping lines nicely at commas
  1691.  
  1692.   Args: field_name  -- The name of the field we're formatting ("TO:", Cc:...)
  1693.         newsgrps    -- ADDRESS structure to format
  1694.         line_prefix -- A prefix string for each line such as "> "
  1695.  
  1696.   Result: A formatted, malloced string is returned.
  1697.  
  1698. The resuling lines formatted are 80 columns wide.
  1699.   ----------------------------------------------------------------------*/
  1700. void
  1701. format_newsgroup_string(field_name, newsgrps, line_prefix, pc)
  1702.     char    *newsgrps;
  1703.     char    *line_prefix, *field_name;
  1704.     gf_io_t  pc;
  1705. {
  1706.     char     buf[MAILTMPLEN];
  1707.     int         trailing = 0, llen, alen, plen = 0;
  1708.     char    *next_ng;
  1709.     
  1710.     if(!newsgrps || !*newsgrps)
  1711.       return;
  1712.     
  1713.     if(line_prefix)
  1714.       gf_puts(line_prefix, pc);
  1715.  
  1716.     gf_puts(field_name, pc);
  1717.  
  1718.     if(line_prefix)
  1719.       plen = strlen(line_prefix);
  1720.  
  1721.     llen = plen + strlen(field_name);
  1722.     while(*newsgrps){
  1723.         for(next_ng = newsgrps; *next_ng && *next_ng != ','; next_ng++);
  1724.         strncpy(buf, newsgrps, next_ng - newsgrps);
  1725.         buf[next_ng - newsgrps] = '\0';
  1726.         newsgrps = next_ng;
  1727.         if(*newsgrps)
  1728.           newsgrps++;
  1729.     alen = strlen(buf);
  1730.     if(!trailing){            /* first time thru, just address */
  1731.         llen += alen;
  1732.         trailing++;
  1733.     }
  1734.     else{                /* else preceding comma */
  1735.         gf_puts(",", pc);
  1736.         llen++;
  1737.  
  1738.         if(alen + llen + 1 > 76){
  1739.         gf_puts(NEWLINE, pc);
  1740.         if(line_prefix)
  1741.           gf_puts(line_prefix, pc);
  1742.  
  1743.         gf_puts("    ", pc);
  1744.         llen = alen + plen + 5;
  1745.         }
  1746.         else{
  1747.         gf_puts(" ", pc);
  1748.         llen += alen + 1;
  1749.         }
  1750.     }
  1751.  
  1752.     if(alen && llen > 76){        /* handle long addresses */
  1753.         register char *q, *p = &buf[alen-1];
  1754.  
  1755.         while(p > buf){
  1756.         if(isspace(*p) && (llen - (alen - (int)(p - buf))) < 76){
  1757.             for(q = buf; q < p; q++)
  1758.               (*pc)(*q);    /* write character */
  1759.  
  1760.             gf_puts(NEWLINE, pc);
  1761.             gf_puts("    ", pc);
  1762.             gf_puts(p, pc);
  1763.             break;
  1764.         }
  1765.         else
  1766.           p--;
  1767.         }
  1768.  
  1769.         if(p == buf)        /* no reasonable break point */
  1770.           gf_puts(buf, pc);
  1771.     }
  1772.     else
  1773.       gf_puts(buf, pc);
  1774.     }
  1775.  
  1776.     gf_puts(NEWLINE, pc);
  1777. }
  1778.  
  1779.  
  1780.  
  1781. /*----------------------------------------------------------------------
  1782.   Format a text field that's part of some raw (non-envelope) message header
  1783.  
  1784.   Args: start --
  1785.         finish --
  1786.     pc -- 
  1787.     prefix --
  1788.  
  1789.   Result: Semi-digested text (RFC 1522 decoded, anyway) written with "pc"
  1790.  
  1791.   ----------------------------------------------------------------------*/
  1792. int
  1793. format_raw_hdr_string(start, finish, pc, prefix)
  1794.     char    *start;
  1795.     char    *finish;
  1796.     gf_io_t  pc;
  1797.     char    *prefix;
  1798. {
  1799.     register char *current;
  1800.     char     ch;
  1801.  
  1802.     ch = *finish;
  1803.     *finish = '\0';
  1804.     current = (char *) rfc1522_decode((unsigned char *)tmp_20k_buf,start,NULL);
  1805.     if(islower(*start))
  1806.       *start = toupper(*start);
  1807.  
  1808.     if(prefix && !gf_puts(prefix, pc))
  1809.       return(FHT_WRTERR);
  1810.  
  1811.     /* output from start to finish */
  1812.     while(*current)
  1813.       if(ISRFCEOL(current)){
  1814.       current += 2;
  1815.  
  1816.       if(!gf_puts(NEWLINE, pc)
  1817.          || (*current && prefix && !gf_puts(prefix, pc)))
  1818.         return(FHT_WRTERR);
  1819.       }
  1820.       else if(!(*pc)(*current++))
  1821.     return(FHT_WRTERR);
  1822.  
  1823.     *finish = ch;
  1824.     return(FHT_OK);
  1825. }
  1826.  
  1827.  
  1828.  
  1829.  
  1830. /*----------------------------------------------------------------------
  1831.     Format a strings describing one unshown part of a Mime message
  1832.  
  1833. Args: number -- A string with the part number i.e. "3.2.1"
  1834.       body   -- The body part
  1835.       type   -- 1 - Not shown, but can be
  1836.                 2 - Not shown, cannot be shown
  1837.                 3 - Can't print
  1838.  
  1839.  
  1840. Result: pointer to formatted string in static buffer
  1841.  
  1842. Note that size of the strings are carefully calculated never to overflow 
  1843. the static buffer:
  1844.     number  < 20,  description limited to 100, type_desc < 200,
  1845.     size    < 20,  second line < 100           other stuff < 60
  1846.  ----*/
  1847. char *
  1848. part_desc(number, body, type)
  1849.      BODY *body;
  1850.      int type;
  1851.      char *number;
  1852. {
  1853.     char *t;
  1854.  
  1855.     sprintf(tmp_20k_buf, "%s  [Part %s, %s%.100s%s%s  %s%s]%s",
  1856.         NEWLINE,
  1857.             number,
  1858.             body->description == NULL ? "" : "\"",
  1859.             body->description == NULL ? "" : body->description,
  1860.             body->description == NULL ? "" : "\"  ",
  1861.             type_desc(body->type, body->subtype, body->parameter, 1),
  1862.             body->type == TYPETEXT ? comatose(body->size.lines) :
  1863.                                      byte_string(body->size.bytes),
  1864.             body->type == TYPETEXT ? " lines" : "",
  1865.         NEWLINE);
  1866.  
  1867.     t = &tmp_20k_buf[strlen(tmp_20k_buf)];
  1868.  
  1869.     switch(type) {
  1870.       case 1:
  1871.         sstrcpy(&t,
  1872.         "  [Not Shown. Use the \"V\" command to view or save this part]");
  1873.     sstrcpy(&t, NEWLINE);
  1874.         break;
  1875.  
  1876.       case 2:
  1877.     sstrcpy(&t, "  [Can not ");
  1878.     if(body->type != TYPEAUDIO && body->type != TYPEVIDEO)
  1879.       sstrcpy(&t, "dis");
  1880.  
  1881.     sstrcpy(&t, 
  1882.         "play this part. Use the \"V\" command to save in a file]");
  1883.     sstrcpy(&t, NEWLINE);
  1884.         break;
  1885.  
  1886.       case 3:
  1887.         sstrcpy(&t, "  [Unable to print this part]");
  1888.     sstrcpy(&t, NEWLINE);
  1889.         break;
  1890.     }
  1891.  
  1892.     return(tmp_20k_buf);
  1893. }
  1894.  
  1895.  
  1896. /*
  1897.  * This could be better.  For example, look at the first two.  The
  1898.  * thing that keeps us from using the same array is that the column number
  1899.  * is stored with each entry and it could be different for each.
  1900.  */
  1901. static struct key help_keys[] =
  1902.        {{"M","Main Menu",KS_MAINMENU},    {NULL,NULL,KS_NONE},
  1903.     {"E","Exit Help",KS_EXITMODE},    {NULL,NULL,KS_NONE},
  1904.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1905.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1906.     {"Y","prYnt",KS_PRINT},        {"Z","Print All",KS_NONE},
  1907.     {"B","Report Bug",KS_NONE},    {"W","WhereIs",KS_WHEREIS}};
  1908. INST_KEY_MENU(help_keymenu, help_keys);
  1909. #define    HLP_MAIN_KEY    0
  1910. #define    HLP_ALL_KEY    9
  1911. #define    HLP_BUG_KEY    10
  1912.  
  1913. static struct key review_keys[] =
  1914.        {{NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1915.     {"E","Exit",KS_EXITMODE},    {NULL,NULL,KS_NONE},
  1916.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1917.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1918.     {"Y","prYnt",KS_PRINT},        {NULL,NULL,KS_NONE},
  1919.     {NULL,NULL,KS_NONE},        {"W","WhereIs",KS_WHEREIS}};
  1920. INST_KEY_MENU(review_keymenu, review_keys);
  1921.  
  1922. static struct key view_keys[] = 
  1923.        {{"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  1924.     {"M","Main Menu",KS_MAINMENU},    {"V","ViewAttch",KS_VIEW},
  1925.     {"P","PrevMsg",KS_PREVMSG},    {"N","NextMsg",KS_NEXTMSG},
  1926.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1927.     {"D","Delete",KS_DELETE},    {"U","Undelete",KS_UNDELETE},
  1928.     {"R","Reply",KS_REPLY},        {"F","Forward",KS_FORWARD},
  1929.  
  1930.     {"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  1931.     {"Q","Quit",KS_EXIT},        {"C","Compose",KS_COMPOSER},
  1932.     {"L","ListFldrs",KS_FLDRLIST},    {"G","GotoFldr",KS_GOTOFLDR},
  1933.     {"I","Index",KS_FLDRINDEX},    {"W","WhereIs",KS_WHEREIS},
  1934.     {"Y","prYnt",KS_PRINT},        {"T","TakeAddr",KS_TAKEADDR},
  1935.     {"S","Save",KS_SAVE},        {"E","Export",KS_EXPORT},
  1936.  
  1937.     {"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  1938.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1939.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1940.     {"J","Jump",KS_JUMPTOMSG},    {"TAB","NextNew",KS_NONE},
  1941.     {"H","HdrMode",KS_HDRMODE},    {"B","Bounce",KS_BOUNCE},
  1942.     {"*","Flag",KS_FLAG},        {"|","Pipe",KS_NONE}};
  1943. INST_KEY_MENU(view_keymenu, view_keys);
  1944. #define VIEW_FULL_HEADERS_KEY 32
  1945. #define BOUNCE_KEY 33
  1946. #define FLAG_KEY 34
  1947. #define VIEW_PIPE_KEY 35
  1948.  
  1949. static struct key nr_anon_view_keys[] = 
  1950.        {{"?","Help",KS_SCREENHELP},    {"W","WhereIs",KS_WHEREIS},
  1951.     {"Q", "Quit",KS_EXIT},        {NULL,NULL,KS_NONE},
  1952.     {"P","PrevMsg",KS_PREVMSG},    {"N","NextMsg",KS_NEXTMSG},
  1953.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1954.     {"F","Fwd Email",KS_FORWARD},    {"J","Jump",KS_JUMPTOMSG},
  1955.     {"I", "Index",KS_FLDRINDEX},    {NULL, NULL,KS_NONE}};
  1956. INST_KEY_MENU(nr_anon_view_keymenu, nr_anon_view_keys);
  1957.  
  1958. static struct key nr_view_keys[] = 
  1959.        {{"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  1960.     {"Q","Quit",KS_EXIT},        {NULL,NULL,KS_NONE},
  1961.     {"P","PrevMsg",KS_PREVMSG},    {"N","NextMsg",KS_NEXTMSG},
  1962.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1963.     {"F","Fwd Email",KS_FORWARD},    {"J","Jump",KS_JUMPTOMSG},
  1964.     {"Y","prYnt",KS_PRINT},        {"S","Save",KS_SAVE},
  1965.  
  1966.     {"?","Help",KS_SCREENHELP},    {"O","OTHER CMDS",KS_NONE},
  1967.     {"E","Export",KS_EXPORT},    {"C","Compose",KS_COMPOSER},
  1968.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1969.     {"I","Index",KS_FLDRINDEX},    {"W","WhereIs",KS_WHEREIS},
  1970.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1971.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE}};
  1972. INST_KEY_MENU(nr_view_keymenu, nr_view_keys);
  1973.  
  1974. static struct key text_att_view_keys[] =
  1975.        {{"?","Help",KS_SCREENHELP},    {NULL,NULL,KS_NONE},
  1976.     {"E","Exit Viewer",KS_EXITMODE},    {NULL,NULL,KS_NONE},
  1977.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1978.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1979.     {"Y","prYnt",KS_PRINT},        {"S","Save",KS_SAVE},
  1980.     {"|","Pipe",KS_NONE},        {"W", "WhereIs",KS_WHEREIS}};
  1981. INST_KEY_MENU(text_att_view_keymenu, text_att_view_keys);
  1982. #define ATT_SAVE_KEY 9
  1983. #define ATT_PIPE_KEY 10
  1984.  
  1985.  
  1986. static struct key simple_view_keys[] =
  1987.        {{"?","Help",KS_SCREENHELP},    {NULL,NULL,KS_NONE},
  1988.     {"Q","Quit Viewer",KS_NONE},    {NULL,NULL,KS_NONE},
  1989.     {NULL,NULL,KS_NONE},        {NULL,NULL,KS_NONE},
  1990.     {"-","PrevPage",KS_PREVPAGE},    {"Spc","NextPage",KS_NEXTPAGE},
  1991.     {"F","Fwd Email",KS_FORWARD},    {"S","Save",KS_SAVE},
  1992.     {NULL,NULL,KS_NONE},        {"W","WhereIs",KS_WHEREIS}};
  1993. INST_KEY_MENU(simple_view_keymenu, simple_view_keys);
  1994. #define SAVE_KEY 9
  1995.  
  1996. #define    STYLE_NAME(s)    (((s) == HelpText || (s) == ComposerHelpText) ? "help"\
  1997.               : ((s) == MessageText) ? "message"\
  1998.               : ((s) == ViewAbookText) ? "expanded entry"\
  1999.               : ((s) == ViewAbookAtt) ? "attachment" : "text")
  2000.  
  2001.  
  2002. /*----------------------------------------------------------------------
  2003.    routine for displaying help and message text on the screen.
  2004.  
  2005.   Args: text          buffer to display
  2006.         title         string with title of text being displayed
  2007.         style         whether we are display a message, help text ...
  2008.     source          what's text: char **, char * or FILE * ???
  2009.  
  2010.  
  2011.    This displays in three different kinds of text. One is an array of
  2012. lines passed in in text_array. The other is a simple long string of
  2013. characters passed in in text. The simple string of characters may have
  2014. another string, the header which is prepended to the text with some
  2015. special processing. The two header.... args specify how format and
  2016. filter the header.
  2017.  
  2018.   The style determines what some of the error messages will be, and
  2019. what commands are available as different things are appropriate for
  2020. help text than for message text etc.
  2021.  
  2022.  ---*/
  2023.  
  2024. void
  2025. scrolltool(text, title, style, source, att)
  2026.     void       *text;
  2027.     char       *title;
  2028.     TextType    style;        /* message, news, etc. */
  2029.     SourceType  source;        /* char **, char * or FILE * */
  2030.     ATTACH_S   *att;        /* used only with style AttachText */
  2031. {
  2032.     register long    cur_top_line,  num_display_lines;
  2033.     int              result, done, ch, found_on, found_on_col,
  2034.              orig_ch, first_view, force, scroll_lines, km_size,
  2035.              cursor_row, cursor_col, km_popped;
  2036.     struct key_menu *km;
  2037.     bitmap_t         bitmap;
  2038.     OtherMenu        what;
  2039.     Pos             whereis_pos;
  2040.  
  2041.     num_display_lines          = SCROLL_LINES(ps_global);
  2042.     km_popped              = 0;
  2043.     ps_global->mangled_screen = 1;
  2044.  
  2045.     what        = FirstMenu;        /* which key menu to display */
  2046.     cur_top_line    = 0;
  2047.     done        = 0;
  2048.     found_on        = -1;
  2049.     found_on_col    = -1;
  2050.     first_view        = 1;
  2051.     force        = 0;
  2052.     ch            = 'x';            /* for first time through */
  2053.     whereis_pos.row = 0;
  2054.     whereis_pos.col = 0;
  2055.  
  2056.     set_scroll_text(scroll_state(SS_NEW), text, cur_top_line, source, style);
  2057.     format_scroll_text();
  2058.  
  2059.     setbitmap(bitmap);
  2060.     if(style == AttachText) {
  2061.       km = &text_att_view_keymenu;
  2062.       if(!att){
  2063.       clrbitn(ATT_SAVE_KEY, bitmap);
  2064.       clrbitn(ATT_PIPE_KEY, bitmap);
  2065.       }
  2066.       else if(F_OFF(F_ENABLE_PIPE, ps_global))
  2067.     clrbitn(ATT_PIPE_KEY, bitmap);
  2068.     }
  2069.     else if(style == SimpleText) {
  2070.     km = &simple_view_keymenu;
  2071.     if(ps_global->anonymous)
  2072.       clrbitn(SAVE_KEY, bitmap);
  2073.     }
  2074.     else if(ps_global->anonymous) {
  2075.     km = &nr_anon_view_keymenu;
  2076.     }
  2077.     else if(ps_global->nr_mode) {
  2078.     km = &nr_view_keymenu;
  2079.     }
  2080.     else if(style == MessageText) {
  2081.     km = &view_keymenu;
  2082. #ifndef DOS
  2083.     if(F_OFF(F_ENABLE_PIPE,ps_global))
  2084. #endif
  2085.       clrbitn(VIEW_PIPE_KEY, bitmap);    /* always clear for DOS */
  2086.     if(F_OFF(F_ENABLE_BOUNCE,ps_global))
  2087.       clrbitn(BOUNCE_KEY, bitmap);
  2088.     if(F_OFF(F_ENABLE_FLAG,ps_global))
  2089.       clrbitn(FLAG_KEY, bitmap);
  2090.     if(F_OFF(F_ENABLE_FULL_HDR,ps_global))
  2091.       clrbitn(VIEW_FULL_HEADERS_KEY, bitmap);
  2092.     }
  2093.     else if(style == ReviewMsgsText || style == ViewAbookText
  2094.             || style == ViewAbookAtt) {
  2095.     km = &review_keymenu;
  2096.     if(style == ReviewMsgsText){
  2097.         cur_top_line=max(0, get_scroll_text_lines()-(num_display_lines-2));
  2098.         if(F_ON(F_SHOW_CURSOR, ps_global)){
  2099.         whereis_pos.row = get_scroll_text_lines() - cur_top_line;
  2100.         found_on        = get_scroll_text_lines() - 1;
  2101.         }
  2102.     }
  2103.     }
  2104.     else {                    /* must be paging help */
  2105.     km = &help_keymenu;
  2106.     if(style == ComposerHelpText) {        /* composer gets minimum */
  2107.         clrbitn(HLP_MAIN_KEY, bitmap);
  2108.         clrbitn(HLP_BUG_KEY, bitmap);
  2109.     }
  2110.  
  2111.     if(style != MainHelpText)        /* only main can "print all" */
  2112.       clrbitn(HLP_ALL_KEY, bitmap);
  2113.     }
  2114.  
  2115.     cancel_busy_alarm(-1);
  2116.  
  2117.     while(!done) {
  2118.     if(km_popped){
  2119.         km_popped--;
  2120.         if(km_popped == 0){
  2121.         clearfooter(ps_global);
  2122.         ps_global->mangled_body = 1;
  2123.         }
  2124.     }
  2125.  
  2126.     if(ps_global->mangled_screen) {
  2127.         ps_global->mangled_header = 1;
  2128.         ps_global->mangled_footer = 1;
  2129.             ps_global->mangled_body   = 1;
  2130.     }
  2131.  
  2132.         if(streams_died())
  2133.           ps_global->mangled_header = 1;
  2134.  
  2135.         dprint(9, (debugfile, "@@@@ current:%ld\n",
  2136.            mn_get_cur(ps_global->msgmap)));
  2137.  
  2138.  
  2139.         /*==================== All Screen painting ====================*/
  2140.         /*-------------- The title bar ---------------*/
  2141.     update_scroll_titlebar(title, style, cur_top_line,
  2142.                    ps_global->mangled_header);
  2143.  
  2144.     if(ps_global->mangled_screen){
  2145.         /* this is the only line not cleared by header, body or footer
  2146.          * repaint calls....
  2147.          */
  2148.         ClearLine(1);
  2149.             ps_global->mangled_screen = 0;
  2150.     }
  2151.  
  2152.         /*---- Scroll or update the body of the text on the screen -------*/
  2153.         cur_top_line        = scroll_scroll_text(cur_top_line,
  2154.                              ps_global->mangled_body);
  2155.     ps_global->redrawer    = redraw_scroll_text;
  2156.         ps_global->mangled_body = 0;
  2157.  
  2158.         /*------------- The key menu footer --------------------*/
  2159.     if(ps_global->mangled_footer) {
  2160.         if(km_popped){
  2161.         FOOTER_ROWS(ps_global) = 3;
  2162.         clearfooter(ps_global);
  2163.         }
  2164.  
  2165.             draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
  2166.         1-FOOTER_ROWS(ps_global),0,what,0);
  2167.         what = SameTwelve;
  2168.         ps_global->mangled_footer = 0;
  2169.         if(km_popped){
  2170.         FOOTER_ROWS(ps_global) = 1;
  2171.         mark_keymenu_dirty();
  2172.         }
  2173.     }
  2174.  
  2175.     /*============ Check for New Mail and CheckPoint ============*/
  2176.         if(new_mail(force, first_view ? 0 : NM_TIMING(ch), 1) >= 0)
  2177.       update_scroll_titlebar(title, style, cur_top_line, 1);
  2178.  
  2179.     if(first_view && num_display_lines >= get_scroll_text_lines())
  2180.       q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(style));
  2181.  
  2182.     force      = 0;        /* may not need to next time around */
  2183.     first_view = 0;        /* check_point a priority any more? */
  2184.  
  2185.     /*==================== Output the status message ==============*/
  2186.     if(km_popped){
  2187.         FOOTER_ROWS(ps_global) = 3;
  2188.         mark_status_unknown();
  2189.     }
  2190.  
  2191.         display_message(ch);
  2192.     if(km_popped){
  2193.         FOOTER_ROWS(ps_global) = 1;
  2194.         mark_status_unknown();
  2195.     }
  2196.  
  2197.     if(F_ON(F_SHOW_CURSOR, ps_global)){
  2198.         if(whereis_pos.row > 0){
  2199.         cursor_row  = SCROLL_LINES_ABOVE(ps_global)
  2200.                 + whereis_pos.row - 1;
  2201.         cursor_col  = whereis_pos.col;
  2202.         }
  2203.         else{
  2204.         cursor_col = 0;
  2205.         /* first new line of text */
  2206.             cursor_row  = SCROLL_LINES_ABOVE(ps_global) +
  2207.             ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
  2208.         }
  2209.     }
  2210.     else{
  2211.         cursor_col = 0;
  2212.         cursor_row = ps_global->ttyo->screen_rows
  2213.                 - SCROLL_LINES_BELOW(ps_global);
  2214.     }
  2215.  
  2216.     MoveCursor(cursor_row, cursor_col);
  2217.  
  2218.     /*================ Get command and validate =====================*/
  2219. #ifdef    _WINDOWS
  2220.     mswin_allowcopy(mswin_readscrollbuf);
  2221.     mswin_setscrollcallback(scroll_scroll_callback);
  2222. #endif
  2223.         ch = read_command();
  2224.         orig_ch = ch;
  2225. #ifdef    _WINDOWS
  2226.     mswin_allowcopy(NULL);
  2227.     mswin_setscrollcallback(NULL);
  2228.     cur_top_line = scroll_state(SS_CUR)->top_text_line;
  2229. #endif
  2230.  
  2231.         if(ch < 0x0100 && isupper(ch))
  2232.       ch = tolower(ch);
  2233.         else if(ch >= PF1 && ch <= PF12 && km->which > 0 && km->which < 3)
  2234.       ch = (km->which == 1) ? PF2OPF(ch) : PF2OOPF(ch);
  2235.  
  2236.     ch = validatekeys(ch);
  2237.  
  2238.     if(km_popped)
  2239.       switch(ch){
  2240.         case NO_OP_IDLE:
  2241.         case NO_OP_COMMAND: 
  2242.         case PF2:
  2243.         case OPF2:
  2244.             case OOPF2:
  2245.         case 'o' :
  2246.         case KEY_RESIZE:
  2247.         case ctrl('L'):
  2248.           km_popped++;
  2249.           break;
  2250.         
  2251.         default:
  2252.           clearfooter(ps_global);
  2253.           break;
  2254.       }
  2255.  
  2256.  
  2257.     /*============= Execute command =======================*/
  2258.         switch(ch){
  2259.  
  2260.             /* ------ Help -------*/
  2261.           case PF1:
  2262.           case OPF1:
  2263.           case OOPF1:
  2264.           case OOOPF1:
  2265.           case '?':
  2266.           case ctrl('G'):
  2267.         if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
  2268.         km_popped = 2;
  2269.         ps_global->mangled_footer = 1;
  2270.         break;
  2271.         }
  2272.  
  2273.         whereis_pos.row = 0;
  2274.             if(ps_global->nr_mode || style == SimpleText
  2275.            || style == ReviewMsgsText || style == ViewAbookText
  2276.            || style == ViewAbookAtt) {
  2277.                 q_status_message(SM_ORDER, 0, 5,
  2278.             "No help text currently available");
  2279.                 break;
  2280.             }
  2281.             if(ch == PF1) {
  2282.             if(style == HelpText || style == MainHelpText)
  2283.                   goto df;
  2284.         if(style == ComposerHelpText)
  2285.                   goto unknown;
  2286.         }
  2287.             if(style == HelpText || style == MainHelpText
  2288.            || style == ComposerHelpText) {
  2289.                 q_status_message(SM_ORDER, 0, 5, "Already in Help");
  2290.         break;
  2291.         }
  2292.  
  2293.         km_size = FOOTER_ROWS(ps_global);
  2294.             if(style == AttachText)
  2295.           helper(h_mail_text_att_view, "HELP FOR ATTACHED TEXT VIEW", 0);
  2296.         else
  2297.           helper(h_mail_view, "HELP FOR MESSAGE TEXT VIEW", 0);
  2298.  
  2299.         if(ps_global->next_screen != main_menu_screen
  2300.            && km_size == FOOTER_ROWS(ps_global)) {
  2301.         /* Have to reset because helper uses scroll_text */
  2302.         num_display_lines      = SCROLL_LINES(ps_global);
  2303.         ps_global->mangled_screen = 1;
  2304.         }
  2305.         else
  2306.           done = 1;
  2307.  
  2308.             break; 
  2309.  
  2310.  
  2311.             /*---------- Roll keymenu ------*/
  2312.           case PF2:
  2313.           case OPF2:
  2314.           case OOPF2:
  2315.       case 'o':
  2316.         if(ps_global->anonymous && ch == PF2)
  2317.           goto whereis;
  2318.         if(km->how_many == 1)
  2319.           goto unknown;
  2320.             if (ch == 'o')
  2321.           warn_other_cmds();
  2322.         what = NextTwelve;
  2323.         ps_global->mangled_footer = 1;
  2324.         break;
  2325.             
  2326.  
  2327.             /* -------- Scroll back one page -----------*/
  2328.           case PF7:
  2329.           case '-':   
  2330.           case ctrl('Y'): 
  2331.           case KEY_PGUP:
  2332.       pageup:
  2333.         whereis_pos.row = 0;
  2334.         if(cur_top_line) {
  2335.         scroll_lines = min(max(num_display_lines -
  2336.             ps_global->viewer_overlap, 1), num_display_lines);
  2337.         cur_top_line -= scroll_lines;
  2338.         if(cur_top_line <= 0){
  2339.             cur_top_line = 0;
  2340.             q_status_message1(SM_INFO, 0, 1, "START of %s",
  2341.             STYLE_NAME(style));
  2342.         }
  2343.         }
  2344.         else
  2345.           q_status_message1(SM_ORDER, 0, 1, "Already at start of %s",
  2346.                 STYLE_NAME(style));
  2347.             break;
  2348.  
  2349.  
  2350.             /*---- Scroll down one page -------*/
  2351.           case PF8:
  2352.           case '+':     
  2353.           case ctrl('V'): 
  2354.           case KEY_PGDN:
  2355.           case ' ':
  2356.       pagedown:
  2357.             if(cur_top_line + num_display_lines < get_scroll_text_lines()){
  2358.         whereis_pos.row = 0;
  2359.         scroll_lines = min(max(num_display_lines -
  2360.             ps_global->viewer_overlap, 1), num_display_lines);
  2361.         cur_top_line += scroll_lines;
  2362.  
  2363.         if(cur_top_line + num_display_lines >= get_scroll_text_lines())
  2364.           q_status_message1(SM_INFO, 0, 1, "END of %s",
  2365.             STYLE_NAME(style));
  2366.             }
  2367.         else{
  2368.         if(style == MessageText
  2369.            && F_ON(F_ENABLE_SPACE_AS_TAB, ps_global)){
  2370.             ch = TAB;
  2371.  
  2372.             if(F_ON(F_ENABLE_TAB_DELETES, ps_global))
  2373.               cmd_delete(ps_global, ps_global->msgmap, 0);
  2374.  
  2375.             goto df;
  2376.         }
  2377.         else
  2378.           q_status_message1(SM_ORDER, 0, 1, "Already at end of %s",
  2379.                     STYLE_NAME(style));
  2380.         }
  2381.  
  2382.             break;
  2383.  
  2384.  
  2385.             /*------ Scroll down one line -----*/
  2386.       case KEY_DOWN:
  2387.       case ctrl('N'):
  2388.             if(cur_top_line + num_display_lines < get_scroll_text_lines()){
  2389.         whereis_pos.row = 0;
  2390.             cur_top_line++;
  2391.         if(cur_top_line + num_display_lines >= get_scroll_text_lines())
  2392.           q_status_message1(SM_INFO, 0, 1, "END of %s",
  2393.                   STYLE_NAME(style));
  2394.         }
  2395.         else
  2396.           q_status_message1(SM_ORDER, 0, 1, "Already at end of %s",
  2397.                   STYLE_NAME(style));
  2398.  
  2399.         break;
  2400.  
  2401.  
  2402.             /* ------ Scroll back up one line -------*/
  2403.           case KEY_UP:
  2404.       case ctrl('P'):
  2405.         whereis_pos.row = 0;
  2406.         if(cur_top_line){
  2407.             cur_top_line--;
  2408.         if(cur_top_line == 0)
  2409.           q_status_message1(SM_INFO, 0, 1, "START of %s",
  2410.                     STYLE_NAME(style));
  2411.         }
  2412.         else
  2413.           q_status_message1(SM_ORDER, 0, 1, "Already at start of %s",
  2414.                 STYLE_NAME(style));
  2415.  
  2416.         break;
  2417.         
  2418.  
  2419.             /*---------- Search text (where is) ----------*/
  2420.           case PF12:
  2421.           case 'w':
  2422.           case ctrl('W'):
  2423.         /* PF12 is not whereis in this case */
  2424.         if(style == MessageText && ch == PF12)
  2425.           goto df;
  2426.  
  2427.           whereis:
  2428.             ps_global->mangled_footer = 1;
  2429.         {long start_row;
  2430.          int  start_col;
  2431.  
  2432.          if(F_ON(F_SHOW_CURSOR,ps_global)){
  2433.          if(found_on < 0
  2434.             || found_on >= get_scroll_text_lines()
  2435.             || found_on < cur_top_line
  2436.             || found_on >= cur_top_line + num_display_lines){
  2437.              start_row = cur_top_line;
  2438.              start_col = 0;
  2439.          }
  2440.          else{
  2441.              if(found_on_col < 0){
  2442.              start_row = found_on + 1;
  2443.              start_col = 0;
  2444.              }
  2445.              else{
  2446.              start_row = found_on;
  2447.              start_col = found_on_col+1;
  2448.              }
  2449.          }
  2450.          }
  2451.          else{
  2452.          start_row = (found_on < 0
  2453.                   || found_on >= get_scroll_text_lines()
  2454.                   || found_on < cur_top_line
  2455.                   || found_on >= cur_top_line + num_display_lines)
  2456.                 ? cur_top_line : found_on + 1,
  2457.          start_col = 0;
  2458.          }
  2459.  
  2460.              found_on = search_text(-3, start_row, start_col,
  2461.                      tmp_20k_buf, &whereis_pos);
  2462.         }
  2463.  
  2464.             if(found_on >= 0) {
  2465.         result = found_on < cur_top_line;
  2466.         if(F_ON(F_FORCE_LOW_SPEED,ps_global) ||
  2467.            ps_global->low_speed ||
  2468.            F_ON(F_SHOW_CURSOR,ps_global)){
  2469.             if((found_on >= cur_top_line + num_display_lines ||
  2470.                found_on < cur_top_line) &&
  2471.                num_display_lines > ps_global->viewer_overlap){
  2472.             cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
  2473.             if(get_scroll_text_lines()-cur_top_line < 5)
  2474.               cur_top_line = max(0,
  2475.                   get_scroll_text_lines()-min(5,num_display_lines));
  2476.             }
  2477.             /* else leave cur_top_line alone */
  2478.         }
  2479.         else{
  2480.             cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
  2481.             if(get_scroll_text_lines()-cur_top_line < 5)
  2482.               cur_top_line = max(0,
  2483.               get_scroll_text_lines()-min(5,num_display_lines));
  2484.         }
  2485.  
  2486.         whereis_pos.row = whereis_pos.row - cur_top_line + 1;
  2487.         found_on_col = whereis_pos.col;
  2488.         if(tmp_20k_buf[0])
  2489.           q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
  2490.         else
  2491.           q_status_message2(SM_ORDER, 0, 3,
  2492.                     "%sFound on line %s on screen",
  2493.                     result ? "Search wrapped to start. " : "",
  2494.                     int2string(whereis_pos.row));
  2495.             }
  2496.         else if(found_on == -1)
  2497.           q_status_message(SM_INFO, 0, 2, "Search cancelled");
  2498.             else
  2499.           q_status_message(SM_ORDER | SM_DING, 0, 3, "Word not found");
  2500.  
  2501.             break; 
  2502.  
  2503.  
  2504.             /*-------------- refresh -------------*/
  2505.           case KEY_RESIZE:
  2506.           case ctrl('L'):
  2507.             num_display_lines          = SCROLL_LINES(ps_global);
  2508.         mark_status_dirty();
  2509.         mark_keymenu_dirty();
  2510.         mark_titlebar_dirty();
  2511.             ps_global->mangled_screen = 1;
  2512.         force                     = 1;
  2513.             break;
  2514.  
  2515.  
  2516.             /*------- no op timeout to check for new mail ------*/
  2517.           case NO_OP_IDLE:
  2518.           case NO_OP_COMMAND:
  2519.             break;
  2520.  
  2521.       case ctrl('M'):
  2522.       case ctrl('J'):
  2523.         if(style != MessageText){
  2524.         q_status_message(SM_ORDER | SM_DING, 0, 2,
  2525.                  "No default command in this screen");
  2526.         break;
  2527.         }            /* else fall thru and handle default */
  2528.  
  2529.  
  2530.         /*------- Other commands of error ------*/
  2531.           default:
  2532.       df:
  2533.         whereis_pos.row = 0;
  2534.         if(style == SimpleText){
  2535.         /*
  2536.          * Yet another pitiful hack.
  2537.          */
  2538.         if(ch == 'f' || ch == PF9){     /* Forward */
  2539.             forward_text(ps_global, text, source);
  2540.         }
  2541.         else if((ch == PF10 || ch == 's') && !ps_global->anonymous){
  2542.             char filename[MAXFOLDER+1];
  2543.             int  rv;
  2544.  
  2545.             filename[0] = '\0';
  2546.             ps_global->mangled_footer = 1;
  2547.             while(1) {
  2548.             rv = optionally_enter(filename, -FOOTER_ROWS(ps_global),
  2549.                           0, MAXPATH, 1, 0,
  2550.                           "File to save text in : ",
  2551.                           NULL, NO_HELP, 0);
  2552.             if(rv == 4)
  2553.               redraw_scroll_text();
  2554.             else if(rv != 3)
  2555.               break;
  2556.             }
  2557.  
  2558.             removing_trailing_white_space(filename);
  2559.             removing_leading_white_space(filename);
  2560.             if(rv == 0 && filename[0]){
  2561.             gf_io_t  pc, gc;
  2562.             STORE_S *store=so_get(FileStar,filename,WRITE_ACCESS);
  2563.  
  2564.             if(store){
  2565.                 char *pipe_err;
  2566.  
  2567.                 gf_set_so_writec(&pc, store);
  2568.                 gf_set_readc(&gc, text, (source == CharStar)
  2569.                             ? strlen((char *)text)
  2570.                             : 0L,
  2571.                      source);
  2572.  
  2573.                 gf_filter_init();
  2574.                 if(pipe_err = gf_pipe(gc, pc)){
  2575.                 q_status_message2(SM_ORDER | SM_DING, 3, 3,
  2576.                           "Problem saving to %s: %s",
  2577.                           filename, pipe_err);
  2578.                 }
  2579.                 else
  2580.                   q_status_message1(SM_ORDER,0,2,"Text saved to %s",
  2581.                            filename);
  2582.  
  2583.                 so_give(&store);
  2584.             }
  2585.             else
  2586.               q_status_message2(SM_ORDER | SM_DING,3,3,
  2587.                         "Can't save to %s: %s",
  2588.                         filename,error_description(errno));
  2589.  
  2590.             }
  2591.             else
  2592.               q_status_message(SM_ORDER | SM_DING, 0, 2,
  2593.                        "Save Cancelled");
  2594.         }
  2595.         else if(ch == PF3 || ch == 'q'){
  2596.             done = 1;
  2597.         }
  2598.         else{
  2599.             q_status_message(SM_ORDER | SM_DING, 0, 2,
  2600.                      "Unknown Command");
  2601.         }
  2602.         }
  2603.         else if(style == AttachText && (ch == 's' || ch == PF10) && att) {
  2604.             /*
  2605.              * This section is an expedient hack to get this working.
  2606.              * We'd probably like to pass a "process_cmd()" in as an
  2607.              * argument or something like that.  Also, save_attachment()
  2608.              * opens and reads the attachment again even though we've
  2609.              * already opened and read it before in this case.
  2610.              */
  2611.         save_attachment(-FOOTER_ROWS(ps_global),
  2612.                 mn_m2raw(ps_global->msgmap, 
  2613.                          mn_get_cur(ps_global->msgmap)),
  2614.                 att);
  2615.                 ps_global->mangled_footer = 1;
  2616.  
  2617.         }
  2618.         else if(style == AttachText && (ch == '|' || ch == PF11) && att
  2619.             && F_ON(F_ENABLE_PIPE, ps_global)) {
  2620.             /*
  2621.              * This section is an expedient hack to get this working.
  2622.              * We'd probably like to pass a "process_cmd()" in as an
  2623.              * argument or something like that.  Also, pipe_attachment()
  2624.              * opens and reads the attachment again even though we've
  2625.              * already opened and read it before in this case.
  2626.              */
  2627.         pipe_attachment(mn_m2raw(ps_global->msgmap, 
  2628.                      mn_get_cur(ps_global->msgmap)),
  2629.                 att);
  2630.                 ps_global->mangled_footer = 1;
  2631.  
  2632.         }
  2633.         else if(style == MessageText){
  2634.             result = process_cmd(ps_global, ps_global->msgmap, ch, 0,
  2635.                      orig_ch, &force);
  2636.             dprint(7, (debugfile, "PROCESS_CMD return: %d\n", result));
  2637.  
  2638.                 if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
  2639.             done = 1;
  2640.         }
  2641.         else if(!scroll_state(SS_CUR)){
  2642.             num_display_lines          = SCROLL_LINES(ps_global);
  2643.             ps_global->mangled_screen = 1;
  2644.         }
  2645.             }
  2646.         else {
  2647.         if(!ps_global->nr_mode
  2648.            && ((ch == 'm' || ch == PF1)
  2649.                && (style == HelpText || style == MainHelpText))) {
  2650.                     /*---------- Main menu -----------*/
  2651.                     ps_global->next_screen = main_menu_screen;
  2652.                     done = 1;
  2653.         }
  2654.         else if(!ps_global->nr_mode
  2655.             && ((ch == 'b' || ch == PF11)
  2656.                 && (style == HelpText || style == MainHelpText))) {
  2657.                     /*---------- report a bug  -----------*/
  2658.             gripe(ps_global);
  2659.             num_display_lines = SCROLL_LINES(ps_global);
  2660.         }
  2661.         else if((ch == 'z' || ch == PF10) && style == MainHelpText){
  2662.             print_all_help();
  2663.                 }else if(!ps_global->nr_mode && (ch == PF3 || ch == 'e')) {
  2664.                     /*----------- Done -----------*/
  2665.                     done = 1;
  2666.                 }else if((ch == 'y' && !ps_global->anonymous) ||
  2667.          (ch == PF9 && !ps_global->nr_mode) ||
  2668.          (ch == PF11 && ps_global->nr_mode)) {
  2669.                     /*----------- Print ------------*/
  2670.             char message[12];
  2671.             if(style == AttachText){
  2672.             if(att)
  2673.               strcpy(message, "attachment ");
  2674.             else
  2675.               strcpy(message, "text ");
  2676.             }
  2677.             else if(style == ViewAbookText)
  2678.               strcpy(message, "expanded entry ");
  2679.             else if(style == ViewAbookAtt)
  2680.               strcpy(message, "attachment ");
  2681.             else
  2682.               strcpy(message, "help text ");
  2683.  
  2684.             print_to_printer(text, source, message);
  2685.                 }
  2686.         else {        /*----------- Unknown command -------*/
  2687. unknown:
  2688.             bogus_command(orig_ch,
  2689.                   (style == MainHelpText || style == HelpText)
  2690.                     ? NULL
  2691.                     : F_ON(F_USE_FK,ps_global) ? "F1" : "?");
  2692.                 }
  2693.             }
  2694.             break;
  2695.  
  2696.         } /* End of switch() */
  2697.  
  2698.     } /* End of while() -- loop executing commands */
  2699.  
  2700.     zero_scroll_text();        /* very important to zero out on return!!! */
  2701.     scroll_state(SS_FREE);
  2702. #ifdef    _WINDOWS
  2703.     scroll_setrange(0);
  2704. #endif
  2705. }
  2706.  
  2707.  
  2708.  
  2709. /*----------------------------------------------------------------------
  2710.       Print text on paper
  2711.  
  2712.     Args:  text -- The text to print out
  2713.        source -- What type of source text is
  2714.        message -- Message for open_printer()
  2715.     Handling of error conditions is very poor.
  2716.  
  2717.   ----*/
  2718. static int
  2719. print_to_printer(text, source, message)
  2720.      void        *text;        /* the data to be printed */
  2721.      SourceType         source;    /* char **, char * or FILE * */
  2722.      char        *message;
  2723. {
  2724.     register char **t;
  2725.  
  2726.     if(open_printer(message) != 0)
  2727.       return(-1);
  2728.  
  2729.     if(source == CharStar && text != (char *)NULL) {
  2730.         print_text((char *)text);
  2731.  
  2732.     } else if(source == CharStarStar && text != (char **)NULL) {
  2733.         for(t = text; *t != NULL; t++) {
  2734.             print_text(*t);
  2735.         print_text(NEWLINE);
  2736.         }
  2737.  
  2738.     } else if(source == FileStar && text != (FILE *)NULL) {
  2739.     size_t n;
  2740.     int i;
  2741.     fseek((FILE *)text, 0L, 0);
  2742.     n = 20480 - 1;
  2743.     while(i=fread((void *)tmp_20k_buf, sizeof(char), n, (FILE *)text)) {
  2744.         tmp_20k_buf[i] = '\0';
  2745.         print_text(tmp_20k_buf);
  2746.     }
  2747.     }
  2748.  
  2749.     close_printer();
  2750.     return(0);
  2751. }
  2752.  
  2753.  
  2754. /*----------------------------------------------------------------------
  2755.    Search text being viewed (help or message)
  2756.  
  2757.       Args: q_line      -- The screen line to prompt for search string on
  2758.             start_line  -- Line number in text to begin search on
  2759.             start_col   -- Column to begin search at in first line of text
  2760.             cursor_pos  -- position of cursor is returned to caller here
  2761.                (Actually, this isn't really the position of the
  2762.                 cursor because we don't know where we are on the
  2763.                 screen.  So row is set to the line number and col
  2764.                 is set to the right column.)
  2765.  
  2766.     Result: returns line number string was found on
  2767.             -1 for cancel
  2768.             -2 if not found
  2769.  ---*/
  2770. int
  2771. search_text(q_line, start_line, start_col, report, cursor_pos)
  2772.     int   q_line;
  2773.     long  start_line;
  2774.     int   start_col;
  2775.     char *report;
  2776.     Pos  *cursor_pos;
  2777. {
  2778.     char        prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1];
  2779.     HelpType    help;
  2780.     int         rc;
  2781.     static char search_string[MAX_SEARCH+1] = { '\0' };
  2782.     static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
  2783.                      {ctrl('Y'), 10, "^Y", "First Line"},
  2784.                      {ctrl('V'), 11, "^V", "Last Line"},
  2785.                      {-1, 0, NULL, NULL}
  2786.                     };
  2787.  
  2788.     report[0] = '\0';
  2789.     sprintf(prompt, "Word to search for [%s] : ", search_string);
  2790.     help = NO_HELP;
  2791.     nsearch_string[0] = '\0';
  2792.  
  2793.     while(1) {
  2794.         rc = optionally_enter(nsearch_string, q_line, 0, MAX_SEARCH, 1, 0,
  2795.                               prompt, word_search_key, help, 0);
  2796.         if(rc == 3) {
  2797.             help = help == NO_HELP ? h_oe_searchview : NO_HELP;
  2798.             continue;
  2799.         }
  2800.     else if(rc == 10){
  2801.         strcpy(report, "Searched to First Line.");
  2802.         cursor_pos->row = 0;
  2803.         cursor_pos->col = 0;
  2804.         return(0);
  2805.     }
  2806.     else if(rc == 11){
  2807.         strcpy(report, "Searched to Last Line."); 
  2808.         cursor_pos->row = max(get_scroll_text_lines() - 1, 0);
  2809.         cursor_pos->col = 0;
  2810.         return(cursor_pos->row);
  2811.     }
  2812.  
  2813.         if(rc != 4)
  2814.           break;
  2815.     }
  2816.  
  2817.     if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
  2818.       return(-1);
  2819.  
  2820.     if(nsearch_string[0] != '\0')
  2821.       strcpy(search_string, nsearch_string);
  2822.  
  2823.     rc = search_scroll_text(start_line, start_col, search_string, cursor_pos);
  2824.     return(rc);
  2825. }
  2826.  
  2827.  
  2828.  
  2829. /*----------------------------------------------------------------------
  2830.   Update the scroll tool's titlebar
  2831.  
  2832.     Args:  title -- title to put on titlebar
  2833.        style -- type of titlebar to draw
  2834.        redraw -- flag to force updating
  2835.  
  2836.   ----*/
  2837. void
  2838. update_scroll_titlebar(title, style, cur_top_line, redraw)
  2839.     char     *title;
  2840.     TextType  style;
  2841.     long      cur_top_line;
  2842.     int          redraw;
  2843. {
  2844.     SCROLL_S *st = scroll_state(SS_CUR);
  2845.     int  num_display_lines = SCROLL_LINES(ps_global);
  2846.     long new_line = (cur_top_line + num_display_lines > st->num_lines)
  2847.              ? st->num_lines
  2848.              : cur_top_line + num_display_lines;
  2849.  
  2850.     if(redraw){
  2851.     set_titlebar(title, ps_global->mail_stream,
  2852.              ps_global->context_current, ps_global->cur_folder,
  2853.              ps_global->msgmap, 1,
  2854.              (style == HelpText || style == MainHelpText
  2855.               || style == ComposerHelpText || style == ViewAbookText
  2856.               || style == ViewAbookAtt)
  2857.                ? TextPercent
  2858.                : (style == SimpleText) 
  2859.                ? FileTextPercent 
  2860.                : MsgTextPercent,
  2861.              new_line, st->num_lines);
  2862.  
  2863.     ps_global->mangled_header = 0;
  2864.     }
  2865.     else if(style == SimpleText || style == HelpText
  2866.         || style == MainHelpText || style == ComposerHelpText
  2867.         || style == ViewAbookText || style == ViewAbookAtt)
  2868.       update_titlebar_lpercent(new_line);
  2869.     else
  2870.       update_titlebar_percent(new_line);
  2871. }
  2872.  
  2873.  
  2874.  
  2875. /*----------------------------------------------------------------------
  2876.   manager of global (to this module, anyway) scroll state structures
  2877.  
  2878.  
  2879.   ----*/
  2880. SCROLL_S *
  2881. scroll_state(func)
  2882.     int func;
  2883. {
  2884.     struct scrollstack {
  2885.     SCROLL_S s;
  2886.     struct scrollstack *prev;
  2887.     } *s;
  2888.     static struct scrollstack *stack = NULL;
  2889.  
  2890.     switch(func){
  2891.       case SS_CUR:            /* no op */
  2892.     break;
  2893.       case SS_NEW:
  2894.     s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
  2895.     memset((void *)s, 0, sizeof(struct scrollstack));
  2896.     s->prev = stack;
  2897.     stack  = s;
  2898.     break;
  2899.       case SS_FREE:
  2900.     if(stack){
  2901.         s = stack->prev;
  2902.         fs_give((void **)&stack);
  2903.         stack = s;
  2904.     }
  2905.     break;
  2906.       default:                /* BUG: should complain */
  2907.     break;
  2908.     }
  2909.  
  2910.     return(stack ? &stack->s : NULL);
  2911. }
  2912.  
  2913.  
  2914.  
  2915. /*----------------------------------------------------------------------
  2916.       Save all the data for scrolling text and paint the screen
  2917.  
  2918.  
  2919.   ----*/
  2920. void
  2921. set_scroll_text(st, text, current_line, source, style)
  2922.     SCROLL_S    *st;
  2923.     void    *text;
  2924.     long     current_line;
  2925.     SourceType     source;
  2926.     TextType     style;
  2927. {
  2928.     /* save all the stuff for possible asynchronous redraws */
  2929.     st->text               = text;
  2930.     st->top_text_line      = current_line;
  2931.     st->screen_start_line  = SCROLL_LINES_ABOVE(ps_global);
  2932.     st->screen_other_lines = SCROLL_LINES_ABOVE(ps_global)
  2933.                 + SCROLL_LINES_BELOW(ps_global);
  2934.     st->source             = source;
  2935.     st->style           = style;
  2936.     st->screen_width       = -1;    /* Force text formatting calculation */
  2937. }
  2938.  
  2939.  
  2940.  
  2941. /*----------------------------------------------------------------------
  2942.      Redraw the text on the screen, possibly reformatting if necessary
  2943.  
  2944.    Args None
  2945.  
  2946.  ----*/
  2947. void
  2948. redraw_scroll_text()
  2949. {
  2950.     int          i, len, offset;
  2951.     SCROLL_S *st = scroll_state(SS_CUR);
  2952.  
  2953.     format_scroll_text();
  2954.  
  2955.     offset = (st->source == FileStar) ? 0 : st->top_text_line;
  2956.  
  2957. #ifdef _WINDOWS
  2958.     mswin_beginupdate();
  2959. #endif
  2960.     /*---- Actually display the text on the screen ------*/
  2961.     for(i = 0; i < PGSIZE(st); i++){
  2962.     ClearLine(i + st->screen_start_line);
  2963.         if((offset + i) < st->num_lines) {
  2964.             len = min(st->line_lengths[offset + i], st->screen_width);
  2965.             PutLine0n8b(i + st->screen_start_line, 0,
  2966.                       st->text_lines[offset + i], len);
  2967.         }
  2968.     }
  2969.  
  2970. #ifdef _WINDOWS
  2971.     mswin_endupdate();
  2972. #endif
  2973.     fflush(stdout);
  2974. }
  2975.  
  2976.  
  2977.  
  2978.  
  2979. /*----------------------------------------------------------------------
  2980.   Free memory used as scrolling buffers for text on disk.  Also mark
  2981.   text_lines as available
  2982.   ----*/
  2983. void
  2984. zero_scroll_text()
  2985. {
  2986.     SCROLL_S     *st = scroll_state(SS_CUR);
  2987.     register int  i;
  2988.  
  2989.     for(i = 0; i < st->lines_allocated; i++)
  2990.       if(st->source == FileStar && st->text_lines[i])
  2991.     fs_give((void **)&st->text_lines[i]);
  2992.       else
  2993.     st->text_lines[i] = NULL;
  2994.  
  2995.     if(st->source == FileStar && st->findex != NULL){
  2996.     fclose(st->findex);
  2997.     st->findex = NULL;
  2998.     if(st->fname){
  2999.         unlink(st->fname);
  3000.         fs_give((void **)&st->fname);
  3001.     }
  3002.     }
  3003.  
  3004.     if(st->text_lines)
  3005.       fs_give((void **)&st->text_lines);
  3006.  
  3007.     if(st->line_lengths)
  3008.       fs_give((void **)&st->line_lengths);
  3009. }
  3010.  
  3011.  
  3012.  
  3013. /*----------------------------------------------------------------------
  3014.  
  3015. Always format at least 20 chars wide. Wrapping lines would be crazy for
  3016. screen widths of 1-20 characters 
  3017.   ----*/
  3018. void
  3019. format_scroll_text()
  3020. {
  3021.     int             i, line_len;
  3022.     char           *p, **pp;
  3023. #ifdef    X_NEW
  3024.     char           *max_line;
  3025. #endif
  3026.     SCROLL_S        *st = scroll_state(SS_CUR);
  3027.     register short  *ll;
  3028.     register char  **tl, **tl_end, *last_space;
  3029.  
  3030.     if(!st || (st->screen_width == (i = ps_global->ttyo->screen_cols)
  3031.            && st->screen_length == PGSIZE(st)))
  3032.         return;
  3033.  
  3034.     st->screen_width = max(20, i);
  3035.     st->screen_length = PGSIZE(st);
  3036.  
  3037.     if(st->lines_allocated == 0) {
  3038.         st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
  3039.         st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
  3040.     memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
  3041.         st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
  3042.     }
  3043.  
  3044.     tl     = st->text_lines;
  3045.     ll     = st->line_lengths;
  3046.     tl_end = &st->text_lines[st->lines_allocated];
  3047.  
  3048.     if(st->source == CharStarStar) {
  3049.         /*---- original text is already list of lines -----*/
  3050.         /*   The text could be wrapped nicely for narrow screens; for now
  3051.              it will get truncated as it is displayed */
  3052.         for(pp = (char **)st->text; *pp != NULL;) {
  3053.             *tl++ = *pp++;
  3054.             *ll++ = st->screen_width;
  3055.             if(tl >= tl_end) {
  3056.         i = tl - st->text_lines;
  3057.                 st->lines_allocated *= 2;
  3058.                 fs_resize((void **)&st->text_lines,
  3059.                           st->lines_allocated * sizeof(char *));
  3060.                 fs_resize((void **)&st->line_lengths,
  3061.                           st->lines_allocated*sizeof(short));
  3062.                 tl     = &st->text_lines[i];
  3063.                 ll     = &st->line_lengths[i];
  3064.                 tl_end = &st->text_lines[st->lines_allocated];
  3065.             }
  3066.         }
  3067.  
  3068.     st->num_lines = tl - st->text_lines;
  3069.     } else if (st->source == CharStar) {
  3070.         /*------ Format the plain text ------*/
  3071.         for(p = (char *)st->text; *p; ) {
  3072.             *tl = p;
  3073.             line_len = 0;            
  3074.             last_space = NULL;
  3075.  
  3076.             while(*p && !(*p == RETURN || *p == LINE_FEED)){
  3077.         if(*p == ESCAPE && (i = match_escapes(p+1))) {
  3078.             while(i--)            /* Don't count escape in len */
  3079.               p++;
  3080.                 } else if(*p == TAB) {
  3081.             while(line_len < st->screen_width
  3082.               && ((++line_len)&0x07) != 0) /* add tab's spaces */
  3083.               ;
  3084.  
  3085.                     p++;
  3086.                     last_space = p;
  3087.                 } else if(*p == TAG_EMBED){    /* this char and next are */
  3088.                     p += 2;            /* embedded data (not shown) */
  3089.                 } else if(*p == ' ') {
  3090.                     last_space = ++p;
  3091.                     line_len++;
  3092.                 } else {
  3093.                     p++;
  3094.                     line_len++;
  3095.                 }
  3096.  
  3097.                 if(line_len >= st->screen_width){
  3098.             if(last_space)
  3099.               p = last_space;
  3100.  
  3101.             break;
  3102.                 }
  3103.             }
  3104.         
  3105.             *ll = p - *tl;
  3106.             ll++; tl++;
  3107.             if(tl >= tl_end) {
  3108.         i = tl - st->text_lines;
  3109.                 st->lines_allocated *= 2;
  3110.                 fs_resize((void **)&st->text_lines,
  3111.                           st->lines_allocated * sizeof(char *));
  3112.                 fs_resize((void **)&st->line_lengths,
  3113.                          st->lines_allocated*sizeof(short));
  3114.                 tl     = &st->text_lines[i];
  3115.                 ll     = &st->line_lengths[i];
  3116.                 tl_end = &st->text_lines[st->lines_allocated];
  3117.             }      
  3118.             if(*p == '\r' && *(p+1) == '\n') 
  3119.               p += 2;
  3120.             else if(*p == '\n' || *p == '\r')
  3121.               p++;
  3122.         }
  3123.  
  3124.     st->num_lines = tl - st->text_lines;
  3125.     }
  3126.     else {
  3127.     /*------ Display text is in a file --------*/
  3128.  
  3129.     /*
  3130.      * This is pretty much only useful under DOS where we can't fit
  3131.      * all of big messages in core at once.  This scheme makes
  3132.      * some simplifying assumptions:
  3133.      *  1. Lines are on disk just the way we'll display them.  That
  3134.      *     is, line breaks and such are left to the function that
  3135.      *     writes the disk file to catch and fix.
  3136.      *  2. We get away with this mainly because the DOS display isn't
  3137.      *     going to be resized out from under us.
  3138.      *
  3139.      * The idea is to use the already alloc'd array of char * as a 
  3140.      * buffer for sections of what's on disk.  We'll set up the first
  3141.      * few lines here, and read new ones in as needed in 
  3142.      * scroll_scroll_text().
  3143.      *  
  3144.      * but first, make sure there are enough buffer lines allocated
  3145.      * to serve as a place to hold lines from the file.
  3146.      *
  3147.      *   Actually, this is also used under windows so the display will
  3148.      *   be resized out from under us.  So I changed the following
  3149.      *   to always
  3150.      *    1.  free old text_lines, which may have been allocated
  3151.      *        for a narrow screen.
  3152.      *    2.  insure we have enough text_lines
  3153.      *    3.  reallocate all text_lines that are needed.
  3154.      *   (tom unger  10/26/94)
  3155.      */
  3156.  
  3157.     /* free old text lines, which may be too short. */
  3158.     for(i = 0; i < st->lines_allocated; i++)
  3159.       if(st->text_lines[i])         /* clear alloc'd lines */
  3160.         fs_give((void **)&st->text_lines[i]);
  3161.  
  3162.         /* Insure we have enough text lines. */
  3163.     if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
  3164.         st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
  3165.  
  3166.         fs_resize((void **)&st->text_lines,
  3167.               st->lines_allocated * sizeof(char *));
  3168.         memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
  3169.         fs_resize((void **)&st->line_lengths,
  3170.               st->lines_allocated*sizeof(short));
  3171.     }
  3172.  
  3173.     /* reallocate all text lines that are needed. */
  3174.     for(i = 0; i <= PGSIZE(st); i++)
  3175.       if(st->text_lines[i] == NULL)
  3176.         st->text_lines[i] = (char *)fs_get(st->screen_width*sizeof(char));
  3177.  
  3178.     tl = &st->text_lines[i];
  3179.  
  3180.     st->num_lines = make_file_index();
  3181.  
  3182.     ScrollFile(st->top_text_line);        /* then load them up */
  3183.  
  3184.     }
  3185.  
  3186. #ifdef    _WINDOWS
  3187.     scroll_setrange (st->num_lines);
  3188. #endif
  3189.  
  3190.     *tl = NULL;
  3191. }
  3192.  
  3193.  
  3194.  
  3195.  
  3196. /*
  3197.  * ScrollFile - scroll text into the st struct file making sure 'line'
  3198.  *              of the file is the one first in the text_lines buffer.
  3199.  *
  3200.  *   NOTE: talk about massive potential for tuning...
  3201.  *         Goes without saying this is still under constuction
  3202.  */
  3203. void
  3204. ScrollFile(line)
  3205.     long line;
  3206. {
  3207.     SCROLL_S     *st = scroll_state(SS_CUR);
  3208.     register int  i;
  3209.              long x;
  3210.  
  3211.     if(line <= 0){        /* reset and load first couple of pages */
  3212.     fseek((FILE *) st->text, 0L, 0);
  3213.     }
  3214.     else{
  3215.     /*** do stuff to get the file pointer into the right place ***/
  3216.     /*
  3217.      * BOGUS: this is painfully crude right now, but I just want to get
  3218.      * it going. 
  3219.      *
  3220.      * possibly in the near furture, an array of indexes into the 
  3221.      * file that are the offset for the beginning of each line will
  3222.      * speed things up.  Of course, this
  3223.      * will have limits, so maybe a disk file that is an array
  3224.      * of indexes is the answer.
  3225.      */
  3226.     fseek(st->findex, (size_t)(line) * sizeof(long), 0);
  3227.     if(fread(&x, sizeof(long), (size_t)1, st->findex) != 1){
  3228.         return;
  3229.     }
  3230.  
  3231.     fseek((FILE *) st->text, x, 0);
  3232.     }
  3233.  
  3234.     for(i = 0; i < PGSIZE(st); i++){
  3235.     if(!st->text_lines || !st->text_lines[i]
  3236.        || fgets(st->text_lines[i],st->screen_width,
  3237.             (FILE *)st->text) == NULL)
  3238.       break;
  3239.  
  3240.     st->line_lengths[i] = strlen(st->text_lines[i]);
  3241.     }
  3242.  
  3243.     for(; i < PGSIZE(st); i++)
  3244.       if(st->text_lines && st->text_lines[i]){/* blank out any unused lines */
  3245.       *st->text_lines[i]  = '\0';
  3246.       st->line_lengths[i] = 0;
  3247.       }
  3248. }
  3249.  
  3250.  
  3251. /*
  3252.  * make_file_index - do a single pass over the file containing the text
  3253.  *                   to display, recording line lengths and offsets.
  3254.  *    NOTE: This is never really to be used on a real OS with virtual
  3255.  *          memory.  This is the whole reason st->findex exists.  Don't
  3256.  *          want to waste precious memory on a stupid array that could 
  3257.  *          be very large.
  3258.  */
  3259. long
  3260. make_file_index()
  3261. {
  3262.     SCROLL_S      *st = scroll_state(SS_CUR);
  3263.     register long  l = 0;
  3264.     long       i = 0;
  3265.  
  3266.     if(!st->findex){
  3267.     if(!st->fname)
  3268.       st->fname = temp_nam(NULL, "pi");
  3269.  
  3270.     if((st->findex = fopen(st->fname,"w+b")) == NULL)
  3271.       return(0);
  3272.     }
  3273.     else
  3274.       fseek(st->findex, 0L, 0);
  3275.  
  3276.     fseek((FILE *)st->text, 0L, 0);
  3277.  
  3278.     fwrite((void *)&i, sizeof(long), (size_t)1, st->findex);
  3279.     while(fgets(tmp_20k_buf, st->screen_width, (FILE *)st->text) != NULL){
  3280.     i = ftell((FILE *)st->text);
  3281.     fwrite((void *)&i, sizeof(long), (size_t)1, st->findex);
  3282.     l++;
  3283.     }
  3284.  
  3285.     fseek((FILE *)st->text, 0L, 0);
  3286.  
  3287.     return(l);
  3288. }
  3289.  
  3290.  
  3291.  
  3292. /*----------------------------------------------------------------------
  3293.      Scroll the text on the screen
  3294.  
  3295.    Args:  new_top_line -- The line to be displayed on top of the screen
  3296.           redraw -- Flag to force a redraw even in nothing changed 
  3297.  
  3298.    Returns: resulting top line
  3299.    Note: the returned line number may be less than new_top_line if
  3300.      reformatting caused the total line count to change.
  3301.  
  3302.  ----*/
  3303. long
  3304. scroll_scroll_text(new_top_line, redraw)
  3305.     long new_top_line;
  3306.     int     redraw;
  3307. {
  3308.     SCROLL_S *st = scroll_state(SS_CUR);
  3309.     int          num_display_lines, len, l;
  3310.  
  3311.     num_display_lines = PGSIZE(st);
  3312.  
  3313.     if(st->top_text_line == new_top_line && !redraw)
  3314.       return(new_top_line);
  3315.  
  3316.     format_scroll_text();
  3317.  
  3318.     if(st->top_text_line >= st->num_lines)    /* don't pop line count */
  3319.       new_top_line = st->top_text_line = max(st->num_lines - 1, 0);
  3320.  
  3321.     if(st->source == FileStar)
  3322.       ScrollFile(new_top_line);        /* set up new st->text_lines */
  3323.  
  3324. #ifdef    _WINDOWS
  3325.     scroll_setrange (st->num_lines);
  3326.     scroll_setpos (new_top_line);
  3327. #endif
  3328.  
  3329.     /* --- 
  3330.        Check out the scrolling situation. If we want to scroll, but BeginScroll
  3331.        says we can't then repaint,  + 10 is so we repaint most of the time.
  3332.       ----*/
  3333.     if(redraw ||
  3334.        (st->top_text_line - new_top_line + 10 >= num_display_lines ||
  3335.         new_top_line - st->top_text_line + 10 >= num_display_lines) ||
  3336.     BeginScroll(st->screen_start_line,
  3337.                     st->screen_start_line + num_display_lines - 1) != 0) {
  3338.         /* Too much text to scroll, or can't scroll -- just repaint */
  3339.         st->top_text_line = new_top_line;
  3340.         redraw_scroll_text();
  3341.     }
  3342.     else{
  3343.     if(new_top_line > st->top_text_line){
  3344.         /*------ scroll down ------*/
  3345.         while(new_top_line > st->top_text_line) {
  3346.         ScrollRegion(1);
  3347.         if(st->source == FileStar)
  3348.           l = num_display_lines - (new_top_line - st->top_text_line);
  3349.         else
  3350.           l = st->top_text_line + num_display_lines;
  3351.         if(l < st->num_lines) {
  3352.             len = min(st->line_lengths[l], st->screen_width);
  3353.             PutLine0n8b(st->screen_start_line + num_display_lines - 1,
  3354.                 0, st->text_lines[l], len);
  3355.         }
  3356.         st->top_text_line++;
  3357.         }
  3358.     }
  3359.     else{
  3360.         /*------ scroll up -----*/
  3361.         while(new_top_line < st->top_text_line) {
  3362.         ScrollRegion(-1);
  3363.         st->top_text_line--;
  3364.         if(st->source == FileStar)
  3365.           l = st->top_text_line - new_top_line;
  3366.         else
  3367.           l = st->top_text_line;
  3368.         len = min(st->line_lengths[l], st->screen_width);
  3369.         PutLine0n8b(st->screen_start_line, 0, st->text_lines[l], len);
  3370.         }
  3371.     }
  3372.  
  3373.     EndScroll();
  3374.     fflush(stdout);
  3375.     }
  3376.  
  3377.     return(new_top_line);
  3378. }
  3379.  
  3380.  
  3381.  
  3382. /*----------------------------------------------------------------------
  3383.       Search the set scrolling text
  3384.  
  3385.    Args:   start_line -- line to start searching on
  3386.        start_col  -- column to start searching at in first line
  3387.            word       -- string to search for
  3388.            cursor_pos -- position of cursor is returned to caller here
  3389.              (Actually, this isn't really the position of the
  3390.               cursor because we don't know where we are on the
  3391.               screen.  So row is set to the line number and col
  3392.               is set to the right column.)
  3393.  
  3394.    Returns: the line the word was found on or -2 if it wasn't found
  3395.  
  3396.  ----*/
  3397. int
  3398. search_scroll_text(start_line, start_col, word, cursor_pos)
  3399.     long  start_line;
  3400.     int   start_col;
  3401.     char *word;
  3402.     Pos  *cursor_pos;
  3403. {
  3404.     SCROLL_S *st = scroll_state(SS_CUR);
  3405.     char      tmp[MAX_SCREEN_COLS+1], *wh, *p;
  3406.     long      l, offset, dlines;
  3407.  
  3408.     dlines = PGSIZE(st);
  3409.     offset = (st->source == FileStar) ? st->top_text_line : 0;
  3410.  
  3411.     if(start_line < st->num_lines){
  3412.     /* search first line starting at position start_col in */
  3413.     strncpy(tmp, st->text_lines[start_line - offset],
  3414.         min(st->line_lengths[start_line - offset], MAX_SCREEN_COLS));
  3415.     tmp[min(st->line_lengths[start_line - offset],
  3416.         MAX_SCREEN_COLS) + 1] = '\0';
  3417.     p = tmp + start_col;
  3418.     if((wh=srchstr(p, word)) != NULL){
  3419.         cursor_pos->row = start_line;
  3420.         cursor_pos->col = wh - tmp;
  3421.         return(start_line);
  3422.     }
  3423.  
  3424.     if(st->source == FileStar)
  3425.       offset++;
  3426.  
  3427.     for(l = start_line+1; l < st->num_lines; l++) {
  3428.         if(st->source == FileStar && l > offset + dlines)
  3429.           ScrollFile(offset += dlines);
  3430.  
  3431.         strncpy(tmp, st->text_lines[l-offset], 
  3432.             min(st->line_lengths[l-offset], MAX_SCREEN_COLS));
  3433.         tmp[min(st->line_lengths[l-offset], MAX_SCREEN_COLS) + 1] = '\0';
  3434.         if((wh=srchstr(tmp, word)) != NULL)
  3435.           break;
  3436.     }
  3437.  
  3438.     if(l < st->num_lines) {
  3439.         cursor_pos->row = l;
  3440.         cursor_pos->col = wh - tmp;
  3441.         return(l);
  3442.     }
  3443.     }
  3444.     else
  3445.       start_line = st->num_lines;
  3446.  
  3447.     if(st->source == FileStar)        /* wrap offset */
  3448.       ScrollFile(offset = 0);
  3449.  
  3450.     for(l = 0; l < start_line; l++) {
  3451.     if(st->source == FileStar && l > offset + dlines)
  3452.       ScrollFile(offset += dlines);
  3453.  
  3454.     strncpy(tmp, st->text_lines[l-offset], 
  3455.         min(st->line_lengths[l-offset], MAX_SCREEN_COLS));
  3456.     tmp[min(st->line_lengths[l-offset], MAX_SCREEN_COLS) + 1] = '\0';
  3457.         if((wh=srchstr(tmp, word)) != NULL)
  3458.           break;
  3459.     }
  3460.  
  3461.     if(l == start_line)
  3462.       return(-2);
  3463.     else{
  3464.     cursor_pos->row = l;
  3465.     cursor_pos->col = wh - tmp;
  3466.         return(l);
  3467.     }
  3468. }
  3469.      
  3470.  
  3471. char *    
  3472. display_parameters(parameter_list)
  3473.      PARAMETER *parameter_list;
  3474. {
  3475.     int        longest_attribute;
  3476.     PARAMETER *p;
  3477.     char      *d;
  3478.  
  3479.     if(parameter_list == NULL) {
  3480.         tmp_20k_buf[0] = '\0';
  3481.  
  3482.     } else {
  3483.         longest_attribute = 0;
  3484.     
  3485.         for(p = parameter_list; p != NULL; p = p->next)
  3486.           longest_attribute = max(longest_attribute, (p->attribute == NULL ?
  3487.                                                       0 :
  3488.                                                       strlen(p->attribute)));
  3489.     
  3490.         longest_attribute = min(longest_attribute, 11);
  3491.     
  3492.         d = tmp_20k_buf;
  3493.         for(p = parameter_list; p != NULL; p = p->next) {
  3494.             sprintf(d, "%-*s: %s\n", longest_attribute,
  3495.                     p->attribute != NULL ? p->attribute : "",
  3496.                     p->value     != NULL ? p->value     : "");
  3497.             d += strlen(d);
  3498.         }
  3499.     }
  3500.     return(tmp_20k_buf);
  3501. }
  3502.  
  3503.  
  3504.  
  3505. /*----------------------------------------------------------------------
  3506.     Display the contents of the given file (likely output from some command)
  3507.  
  3508.   Args: filename -- name of file containing output
  3509.     title -- title to be used for screen displaying output
  3510.     alt_msg -- if no output, Q this message instead of the default
  3511.   Returns: none
  3512.  ----*/
  3513. void
  3514. display_output_file(filename, title, alt_msg)
  3515.     char *filename, *title, *alt_msg;
  3516. {
  3517.     int   msg_q = 0, i = 0;
  3518.     char  buf[512], *msg_p[4];
  3519.     FILE *f;
  3520. #define    MAX_SINGLE_MSG_LEN    60
  3521.  
  3522.     if(f = fopen(filename, "r")){
  3523.     buf[0]   = '\0';
  3524.     msg_p[0] = buf;
  3525.     while(fgets(msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf), f) 
  3526.           && msg_q < 3 && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
  3527.         msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
  3528.         if (*(msg_p[++msg_q] - 1) == '\n')
  3529.           *(msg_p[msg_q] - 1) = '\0';
  3530.     }
  3531.  
  3532.     if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
  3533.         if(*msg_p[0])
  3534.           for(i = 0; i < msg_q; i++)
  3535.         q_status_message2(SM_ORDER, 3, 4,
  3536.             "%s Result: %s", title, msg_p[i]);
  3537.         else
  3538.           q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
  3539.         alt_msg ? alt_msg : " command completed");
  3540.     }
  3541.     else{
  3542.         scrolltool(f, title, AttachText, FileStar, NULL);
  3543.         ps_global->mangled_screen = 1;
  3544.     }
  3545.  
  3546.     fclose(f);
  3547.     unlink(filename);
  3548.     }
  3549.     else
  3550.       dprint(2, (debugfile, "Error reopening %s to get results: %s\n",
  3551.          filename, error_description(errno)));
  3552. }
  3553.  
  3554.  
  3555.  
  3556. /*----------------------------------------------------------------------
  3557.       Fetch the requested header fields from the msgno specified
  3558.  
  3559.    Args: stream -- mail stream of open folder
  3560.          msgno -- number of message to get header lines from
  3561.          fields -- array of pointers to desired fields
  3562.  
  3563.    Returns: allocated string containing matched header lines,
  3564.         NULL on error.
  3565.  ----*/
  3566. char *
  3567. xmail_fetchheader_lines(stream, msgno, fields)
  3568.      MAILSTREAM  *stream;
  3569.      long         msgno;
  3570.      char       **fields;
  3571. {
  3572.     int   i, old_prefetch;
  3573.     char *p, *m, *h = NULL, *match = NULL, tmp[MAILTMPLEN];
  3574.     extern int find_field();
  3575.  
  3576.     old_prefetch = (int) mail_parameters(stream, GET_PREFETCH, NULL);
  3577.     (void) mail_parameters(stream, SET_PREFETCH, NULL);
  3578.     h         = mail_fetchheader(stream, msgno);
  3579.     mail_parameters(stream, SET_PREFETCH, (void *) old_prefetch);
  3580.  
  3581.     if(!h)
  3582.       return(NULL);                /* fetchheader return error? */
  3583.  
  3584.     while(find_field(&h, tmp)){
  3585.     for(i = 0; fields[i] && strucmp(tmp, fields[i]); i++)
  3586.       ;
  3587.  
  3588.     if(p = fields[i]){            /* interesting field */
  3589.         /*
  3590.          * Hold off allocating space for matching fields until
  3591.          * we at least find one to copy...
  3592.          */
  3593.         if(!match)
  3594.           match = m = fs_get(strlen(h) + strlen(p) + 1);
  3595.  
  3596.         while(*p)                /* copy field name */
  3597.           *m++ = *p++;
  3598.  
  3599.         while(*h && (*m++ = *h++))        /* header includes colon */
  3600.           if(*(m-1) == '\n' && (*h == '\r' || !isspace(*h)))
  3601.         break;
  3602.  
  3603.         *m = '\0';                /* tie off match string */
  3604.     }
  3605.     else{                    /* no match, pass this field */
  3606.         while(*h && !(*h++ == '\n' && (*h == '\r' || !isspace(*h))))
  3607.           ;
  3608.     }
  3609.     }
  3610.  
  3611.     return(match ? match : cpystr(""));
  3612. }
  3613.  
  3614.  
  3615. /*----------------------------------------------------------------------
  3616.    Fetch everything except the requested header fields from given msgno
  3617.  
  3618.    Args: stream -- mail stream of open folder
  3619.          msgno -- number of message to get header lines from
  3620.          fields -- array of pointers to UNdesired fields
  3621.  
  3622.    Returns: allocated string containing NON-matching header lines,
  3623.         NULL on error.
  3624.  ----*/
  3625. char *
  3626. xmail_fetchheader_lines_not(stream, msgno, fields)
  3627.      MAILSTREAM  *stream;
  3628.      long         msgno;
  3629.      char       **fields;
  3630. {
  3631.     int   i, old_prefetch;
  3632.     char *p, *m, *h = NULL, *match = NULL, tmp[MAILTMPLEN];
  3633.     extern int find_field();
  3634.  
  3635.     old_prefetch = (int) mail_parameters(stream, GET_PREFETCH, NULL);
  3636.     (void) mail_parameters(stream, SET_PREFETCH, NULL);
  3637.     h         = mail_fetchheader(stream, msgno);
  3638.     mail_parameters(stream, SET_PREFETCH, (void *) old_prefetch);
  3639.  
  3640.     if(!h)
  3641.       return(NULL);                /* fetchheader return error? */
  3642.  
  3643.     while(find_field(&h, tmp)){
  3644.     for(i = 0; fields[i] && strucmp(tmp, fields[i]); i++)
  3645.       ;
  3646.  
  3647.     if(!fields[i]){                /* interesting field */
  3648.         /*
  3649.          * Hold off allocating space for matching fields until
  3650.          * we at least find one to copy...
  3651.          */
  3652.         if(!match)
  3653.           match = m = fs_get(strlen(h) + strlen(tmp) + 1);
  3654.  
  3655.         sstrcpy(&m, tmp);            /* copy field name */
  3656.         while(*h && (*m++ = *h++))        /* header includes colon */
  3657.           if(*(m-1) == '\n' && (*h == '\r' || !isspace(*h)))
  3658.         break;
  3659.  
  3660.         *m = '\0';                /* tie off match string */
  3661.     }
  3662.     else{                    /* no match, pass this field */
  3663.         while(*h && !(*h++ == '\n' && (*h == '\r' || !isspace(*h))))
  3664.           ;
  3665.     }
  3666.     }
  3667.  
  3668.     return(match ? match : cpystr(""));
  3669. }
  3670.  
  3671.  
  3672. int
  3673. find_field(h, tmp)
  3674.      char **h;
  3675.      char *tmp;
  3676. {
  3677.     if(!h || !*h || !**h || isspace(**h))
  3678.       return(0);
  3679.  
  3680.     while(**h && **h != ':' && !isspace(**h))
  3681.       *tmp++ = *(*h)++;
  3682.  
  3683.     *tmp = '\0';
  3684.     return(1);
  3685. }
  3686.  
  3687.  
  3688. #ifdef    _WINDOWS
  3689. /*----------------------------------------------------------------------
  3690.     Return characters in scroll tool buffer serially
  3691.  
  3692.    Args: n -- index of char to return
  3693.  
  3694.    Returns: returns the character at index 'n', or -1 on error or
  3695.         end of buffer.
  3696.  
  3697.  ----*/
  3698. int
  3699. mswin_readscrollbuf(n)
  3700.     int n;
  3701. {
  3702.     SCROLL_S *st = scroll_state(SS_CUR);
  3703.     int       c;
  3704.     static char **orig = NULL, **l, *p;
  3705.     static int    lastn;
  3706.  
  3707.     if(!st)
  3708.       return(-1);
  3709.  
  3710.     /*
  3711.      * All of these are mind-numbingly slow at the moment...
  3712.      */
  3713.     switch(st->source){
  3714.       case CharStar :
  3715.     return((n >= strlen((char *)st->text)) ? -1 : ((char *)st->text)[n]);
  3716.  
  3717.       case CharStarStar :
  3718.     /* BUG? is this test rigorous enough? */
  3719.     if(orig != (char **)st->text || n < lastn){
  3720.         lastn = n;
  3721.         if(orig = l = (char **)st->text)    /* reset l and p */
  3722.           p = *l;
  3723.     }
  3724.     else{                /* use cached l and p */
  3725.         c = n;            /* and adjust n */
  3726.         n -= lastn;
  3727.         lastn = c;
  3728.     }
  3729.  
  3730.     while(l){            /* look for 'n' on each line  */
  3731.         for(; n && *p; n--, p++)
  3732.           ;
  3733.  
  3734.         if(n--)            /* 'n' found ? */
  3735.           p = *++l;
  3736.         else
  3737.           break;
  3738.     }
  3739.  
  3740.     return((l && *l) ? *p ? *p : '\n' : -1);
  3741.  
  3742.       case FileStar :
  3743.     return((fseek((FILE *)st->text, (long) n, 0) < 0
  3744.         || (c = fgetc((FILE *)st->text)) == EOF) ? -1 : c);
  3745.  
  3746.       default:
  3747.     return(-1);
  3748.     }
  3749. }
  3750.  
  3751.  
  3752.  
  3753. /*----------------------------------------------------------------------
  3754.      MSWin scroll callback.  Called during scroll message processing.
  3755.          
  3756.  
  3757.  
  3758.   Args: cmd - what type of scroll operation.
  3759.     scroll_pos - paramter for operation.  
  3760.             used as position for SCROLL_TO operation.
  3761.  
  3762.   Returns: TRUE - did the scroll operation.
  3763.        FALSE - was not able to do the scroll operation.
  3764.  ----*/
  3765. int
  3766. scroll_scroll_callback (cmd, scroll_pos)
  3767. int    cmd;
  3768. long    scroll_pos;
  3769. {
  3770.     SCROLL_S   *st = scroll_state(SS_CUR);
  3771.     int        paint = FALSE;
  3772.     int        num_display_lines;
  3773.     int        scroll_lines;
  3774.     int        num_text_lines;
  3775.     char    *message, *msgSTART, *msgEND;
  3776.     long    maxscroll;
  3777.     
  3778.     
  3779.     msgSTART = "START of message text";
  3780.     msgEND = "END of message text";
  3781.     message = NULL;
  3782.     maxscroll = st->num_lines;
  3783.     switch (cmd) {
  3784.     case MSWIN_KEY_SCROLLUPLINE:
  3785.     if(st->top_text_line > 0) {
  3786.         st->top_text_line--;
  3787.         paint = TRUE;
  3788.         if (st->top_text_line <= 0)
  3789.           message = msgSTART;
  3790.         }
  3791.     break;
  3792.  
  3793.     case MSWIN_KEY_SCROLLDOWNLINE:
  3794.         if(st->top_text_line < maxscroll) {
  3795.         st->top_text_line++;
  3796.         paint = TRUE;
  3797.         if (st->top_text_line >= maxscroll) 
  3798.           message = msgEND;
  3799.         }
  3800.     break;
  3801.         
  3802.     case MSWIN_KEY_SCROLLUPPAGE:
  3803.     if(st->top_text_line > 0) {
  3804.         num_display_lines = SCROLL_LINES(ps_global);
  3805.         scroll_lines = min(max(num_display_lines -
  3806.             ps_global->viewer_overlap, 1), num_display_lines);
  3807.         if (st->top_text_line > scroll_lines)
  3808.         st->top_text_line -= scroll_lines;
  3809.         else {
  3810.         st->top_text_line = 0;
  3811.         message = msgSTART;
  3812.         }
  3813.         paint = TRUE;
  3814.         }
  3815.     break;
  3816.         
  3817.     case MSWIN_KEY_SCROLLDOWNPAGE:
  3818.     num_display_lines = SCROLL_LINES(ps_global);
  3819.     if(st->top_text_line  < maxscroll) {
  3820.         scroll_lines = min(max(num_display_lines -
  3821.             ps_global->viewer_overlap, 1), num_display_lines);
  3822.         st->top_text_line += scroll_lines;
  3823.         if (st->top_text_line >= maxscroll) {
  3824.         st->top_text_line = maxscroll;
  3825.         message = msgEND;
  3826.         }
  3827.         paint = TRUE;
  3828.     }
  3829.     break;
  3830.         
  3831.     case MSWIN_KEY_SCROLLTO:
  3832.     if (st->top_text_line != scroll_pos) {
  3833.         st->top_text_line = scroll_pos;
  3834.         if (st->top_text_line == 0)
  3835.         message = msgSTART;
  3836.         else if(st->top_text_line >= maxscroll) 
  3837.         message = msgEND;
  3838.         paint = TRUE;
  3839.         }
  3840.     break;
  3841.     }
  3842.     
  3843.     if (paint) {
  3844.     mswin_beginupdate();
  3845.     update_scroll_titlebar(NULL, st->style, st->top_text_line, 0);
  3846.     (void) scroll_scroll_text(st->top_text_line, 1);
  3847.     if (message != NULL)
  3848.       q_status_message(SM_INFO, 0, 1, message);
  3849.  
  3850.     /* Display is always called so that the "START(END) of message" 
  3851.      * message gets removed when no longer at the start(end). */
  3852.     display_message (KEY_PGDN);
  3853.     mswin_endupdate();
  3854.     }
  3855.  
  3856.     return (TRUE);
  3857. }
  3858. #endif
  3859.